aboutsummaryrefslogtreecommitdiffstats
path: root/java
Commit message (Collapse)AuthorAgeFilesLines
* Add DBTEnv::DEBUG_JNI 'direct_bt.debug.jni' for JNI only related ↵Sven Gothel2020-10-141-10/+10
| | | | DBG_JNI_PRINT(); IRQ_PRINT(..) shall not print_backtrace().
* dbt_debug: Have DBG_PRINT and WORDY_PRINT as macros, suppressing argument ↵Sven Gothel2020-10-081-6/+1
| | | | | | | | | evaluation if condition is false Following commit 581260d3f4144df3716eac736c9e8792be264edb Also reverting COND_PRINT to drop __func__, __FILE__ and __LINE__ as not intended for this verbose ourput instrument (like DBG_PRINT etc).
* JNI BluetoothUtils::decodeUTF8String(): Limit scope of JNICriticalArray, not ↵Sven Gothel2020-09-301-6/+8
| | | | entering another JNI call - hence removes jni-check warnings.
* JNI helper_base.hpp: java_exception_check_and_throw(..) after adding object ↵Sven Gothel2020-09-301-0/+3
| | | | to array
* JNI helper_base.cxx: Fix ArrayList class name, drop decorating 'L' and ';'Sven Gothel2020-09-301-1/+1
|
* Make clang++ 9.0 happy (no warnings)Sven Gothel2020-09-281-2/+2
|
* DBTAdapter.cxx [add|remove]StatusListener(): add-case needs setInstance ↵Sven Gothel2020-09-261-0/+4
| | | | before adding to receive the initial setting event; Use more readable getInstanceUnchecked() and clearInstance()
* Handle abort() via dbt_debug's new ABORT(..), ensure a message is being send ↵Sven Gothel2020-09-201-2/+1
| | | | and process aborted; Use it.
* POctets::malloc: throw new exception type OutOfMemoryError early on malloc ↵Sven Gothel2020-09-192-1/+8
| | | | | | | | | | | failure, also throw IllegalArgumentException on size <= 0 - Add exception type 'direct_bt::OutOfMemoryError' and wire it in helper_base for C++/JNI interoperability. - std::malloc may not return nullptr on zero size, but a valid dummy pointer usable by free. Hence check for malloc(0) and throw IllegalArgumentException - also throw OutOfMemoryError on nullptr == malloc(size)
* C++/JNI: helper_base.hpp/cxx: Rework raise_java_exception(..) and ↵Sven Gothel2020-09-192-119/+131
| | | | | | | | | | | | | | | | | | | rethrow_and_raise_java_exception[->_impl](..) .. Commit 5cfe7e13c4dbf07360d928e421050de5e00684a1 intention was noble, however, 'inline' is not guaranteed and doesn't work here. I.e. the desired inline reduction with automatic elision and using __FILE__ and __LINE__ macros is not supported. Instead, move the functions back to helper_base.cxx and add paramter for __FILE__ and __LINE__ to be passed by the only caller macro 'rethrow_and_raise_java_exception(E)'. The latter simply explodes to 'rethrow_and_raise_java_exception_impl((E), __FILE__, __LINE__)', which serves the purposed of having exposed the file and line position where the exceotion got caught. Also support api/tinyb/BluetoothException, rendering commit 3139f93409cac61ba5b80caf506375d94eff8778 valid, i.e. use rethrow_and_raise_java_exception(..) in the tinyb C++/JNI code instead of exploded exception case blocks.
* C++ tinyb/general: Use rethrow_and_raise_java_exception(..) instead of ↵Sven Gothel2020-09-191-16/+4
| | | | exploded exception case blocks
* helper_base.hpp: Have all raise_java_exception(..) and ↵Sven Gothel2020-09-192-111/+126
| | | | | | | rethrow_and_raise_java_exception(..) inline, benefitting from __FILE__ and __LINE__ Also have all raise_java_exception(..) call print_exception(..) upfront unconditionally, to not miss any exception in case a thread has died or the Java caller suppresses the output.
* C++ noexcept: JavaUplink, DBTObject, JavaGlobalObjSven Gothel2020-09-151-11/+11
|
* C++ noexcept: JNIMem* (convert exception in dtor to abort)Sven Gothel2020-09-152-17/+19
|
* Adapt to new DBG_PRINT(..) semantics, reduce verbosity, use specific macros ↵Sven Gothel2020-08-241-5/+2
| | | | for special cases (e.g. GATT_PRINT, ..)
* BluetoothFactory/DBTEnv: Pass JVM properties to environment, access DEBUG, ↵Sven Gothel2020-08-241-0/+24
| | | | | | | | | | | | | | | | | | | | | | | VERBOSE via lazy DBTEnv from C++ ( DEBUG := environment 'direct_bt_debug' or JVM property 'direct_bt.debug' VERBOSE := environment 'direct_bt_verbose' or JVM property 'direct_bt.verbose' This changes allows passing JVM properties as C++ environment variables, to be accessed via DBTEnv. JVM property names are renamed from 'foo.bar' to 'jvm_foo_bar' and can be queried via 'DBTEnv::getProperty("foo_bar")' as it will also attempt the 'jvm_' prefix if the plain name wasn't resolved. The singleton DBTEnv instance can be retrieved via DBTEnv::get(), which allows lazy initialization of DEBUG, VERBOSE from environment variables. This is required, as the JVM loads the native libraries first, initializes all native static variables and only then can pass the properties to the native environment via POSIX 'setenv(..)'. Hence users should never use static initialization from native code in such cases, otherwise they can't benefit from the unified JVM properties.
* JNI: Complete C++ to Java Exception mappingSven Gothel2020-07-293-29/+77
|
* Have GATT[Service,Characteristic,Descriptor] derived from DBTObject for ↵Sven Gothel2020-07-261-0/+31
| | | | valid check; Use new JNI getInstance() -> getDBTObject() w/ valid check
* DBTGattCharacteristic::configNotificationIndicationImpl(..): Tolerate ↵Sven Gothel2020-07-241-0/+7
| | | | deleted native instance @ disable
* Reworking GATTCharacteristicListener (C++ and Java)Sven Gothel2020-07-243-4/+8
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | - Aligned all related C++ and Java API doc entries and made sure it matches implementation. - Renaming SpecificGATTCharacteristicListener to AssociatedGATTCharacteristicListener, as we refer to the associate GATTCharacteristic of a GATTCharacteristicListener AssociatedGATTCharacteristicListener is a specialization knowing its associated GATTCharacteristic to be used for match(). - Renamed 'configIndicationNotification(..)' to 'configNotificationIndication(..)', matching order of arguments and the returned enabledState array. - Exposed 'configNotificationIndication(..)' incl enabledState array to Java - Clarified the 'add listener' and 'configNotificationIndication(..)' semantic in API doc and implementation. Added new API entries to distinguish them. - DBTGattCharacteristic.java skips adding its 'TinyB API compatibility' GATTCharacteristicListener in case neither notify nor indicate property exist. Also skip the native configNotificationIndication(..) in such case. This reduces the overall listener load to GattHandler by factor 5! - General: Add new method 'removeAllAssociatedCharacteristicListener(GATTCharacteristic)', allowing removal of all GATTCharacteristic associated listener. This is usefull to complete the GATTCharacteristic C++ dtor or Java close() operation. - DBTDevice: Add GATTCharacteristicListener methods to align with Java API and allow user not to deal with GATTHandler directly. Convenience and validates the C++/Java API alignement. - C++ JNICriticalArray: Added 2nd template typename for the java-array-type, enabling it for other than jbyteArray. Here used for a jbooleanArray. - The GATTCharacteristicListener Java to C++ native holding specialization JNICharacteristicListener keeps a new global reference to the Java GATTCharacteristicListener and the optional associated GATTCharacteristic. This ensures the instances won't get garbage collected and hence ensures proper object lifecycle even when passing 'throw away' listener object created just for the add*Listener call. - Removal and hence destruction of the listeners is always guaranteed at: -- Device / GATTHandler disconnect -- GattCharacteristic dtor or close
* Extract dfa_utf8_decode.[cpp/hpp] added to libtinyb.so, i.e. used for both ↵Sven Gothel2020-07-041-0/+28
| | | | native libs commonly exposed in Java via BluetoothUtils.cxx JNI
* tinyb: fix jni unresolved symbols (add namespace to implementationSven Gothel2020-07-041-4/+4
|
* Notify DBTNativeDownlink when its native JavaUplink -> JavaGlobalObj ↵Sven Gothel2020-06-272-2/+16
| | | | | | | | | | | counterpart gets destructed DBTNativeDownlink.notifyDeleted() gets called from native destructor, so isValid and nativeInstance can be set accordingly. This resolves a periodic crash when GATTHandler flushes it's GATTServices and linked resources. Here the DBTGATTService.java's DBTNativeDownlink still holds the native instance handle and as it gets finalized after the native object, accessing it post mortem causes a SIGSEGV.
* Direct-BT: API doc and clarify interface JavaAnonObj, JavaUplink and ↵Sven Gothel2020-06-181-1/+9
| | | | | | | JavaAnonObj implementation JavaGlobalObj JavaAnonObj is now a full virtual interface. JavaGlobalObj gets default spec for copy and move ctor and assignment.
* DBTAdapter::JNIAdapterStatusListener: Use JNIGlobalRef directly w/o ↵Sven Gothel2020-06-082-0/+8
| | | | | | | | | unique_ptr; Allow JNIGlobalRef default nullptr ctor Since we allow JNIGlobalRef move ops and hence jobject nullptr anyways (on moved off objects), we shall also allow simple default ctor for lazy assignment. This allows us to use JNIGlobalRef directly in JNIAdapterStatusListener w/o extra unique_ptr abstraction.
* Expose UUID strings in TinyB compatible fashion (unified uuid128_t) as an ↵Sven Gothel2020-05-182-0/+19
| | | | | | | | | | | | | option (default = true) Via DBTManager.setUnifyUUID128Bit(boolean): Enables or disables uuid128_t consolidation for native uuid16_t and uuid32_t values before string conversion. Default is {@code true}, as this represent compatibility with original TinyB D-Bus behavior. If desired, this value should be set once before the first call of {@link #getBluetoothManager()}!
* Working GATT Java Side; GATT Types made fully functional for user to avoid ↵Sven Gothel2020-05-171-2/+83
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 'technical' GATTHandler GATT Types made fully functional for user to avoid 'technical' GATTHandler (C++) > GATTService, GATTCharacteristic, GATTDescriptor -- Reside in their own respective files -- Added semantic methods (readValue(), ..) implemented using DBTDevice -> GATTHandler -- GATTDescriptor owns its value -- GATTHandler setSendIndicationConfirmation(..) defaults to true -- Allow user to cirvumvent using GATTHandler manually completely, device 1--*> services 1--*> characteristics 1--*> descriptor -- C++ GATT types aligned 1:1 to TinyB (Java) > Merged GATTIndicationListener + GATTNotificationListener -> GATTCharacteristicListener -- Simplifying usage, unifying notification and indication -- Now using a list of shared_ptr<GATTCharacteristicListener> allowing multiple actors instead of just a one shot entry. Similar to AdapterStatusListener, we utilize this also on the Java side to implement the TinyB notifications. See dbt_scanner00.cpp: Simplified high-level usage. See dbt_scanner01.cpp: Lower-level technical usage w/ GATTHandler. +++ > Simplified more names > Removed redundancy in listener callbacks, -- don't pass adapter when device is already given. device <*--1> adapter -- don't pass GATT handle explicitly when characteristic is passed > Comparison of all GATT types are done by their respective unique handle Attribute handles are unique for each device (server) (BT Core Spec v5.2: Vol 3, Part F Protocol..: 3.2.2 Attribute Handle) > GATTHandler: Own L2CAPComm instance directly, instead of shared_ptr. > JNIMem: Added JNICriticalArray class for RAII style release ++++ ++++ Working GATT Java Side > All toString() methods return the C++ toString() via JNI for better representation. > DBTDevice.java/JNI: Resolved the odd 'adapter' reference issue: -- Was not passing the jobject of DBTAdapter, but its shared container refeference ;-) > All GATT types receive their GATT handler for equal test and identity @ ctor > GATT read/write Value update the cached value as well as issue the notifyValue on change, including GATTCharacteristic notification/indication listener
* jni helper: Unique rename for 'convert_vector*(..)' template functionsSven Gothel2020-05-143-43/+9
|
* Fixing multiple Java/C++ Lifecycle Issues (DBTDevice, add ShutdownHook, ..)Sven Gothel2020-05-123-0/+21
| | | | | | | | | | | | | | | | | | | | | | DBTDevice - don't native delete @ JNI deleteImpl, adapter holds share_ptr ownership - only create its java object 1st time @ deviceFound callback of JNI DBTAdapter DBTAdapter::mgmtEvDeviceFoundCB - in case of !discoveredDeviceList but sharedDeviceList, the device shall be updated first, then deviceFound callbacks issued, allowing listener to act and register, then deviceUpdate callbacks issued, allowing data update on existing actors DBTManager.java: - Add ShutdownHook calling custom hooks and shutdown() - shutdown() in depth shutdown: -- iterated through all adapter issueing adapter.close() -- Adapter.close() iterates through all discoveredDevices issuing close() DBTDevice.java: - adds adapter JNI proxy to removeStatusListener(..), same 'to be resolved' issue as with addStatusListener(..).
* Converging Java/JNI and C++ API to match tinyb interface requirements (step-2)Sven Gothel2020-05-102-0/+32
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | - BasicTypes: Align exception names w/ Java. - BTTypes / EInfoReport: Add getDeviceIDModalias() - DBTManager: Add getConnectionInfo(..) and setLocalName(..) - OctetTypes: Fix put_octets(..), removed sizeof(). Adding put/get string methods +++ DBTTypes: - Fix Comments - DBTDevice: Add appearance, getConnectionInfo(), connectGATT(..) and disconnectGATT(). Last two GATT's ease association of GATTHandler w/ device for tinyb binding. - DBTDevice: Fix defaultConnect(): Differenciate le public/random - DBTDevice: New getConnectionInfo() - also may issue deviceUpdate callback on rssi/tx_power changes - DBTAdapter: Add missing DBTAdapterStatusListener list declaration of previous commit. - DBTAdapter: Add misc information access for tinyb binding. +++ Java - DBTAdapter: Adding more tinyb implementation code, sorting methods - ~90% complete - DBTDevice: Adding more tinyb implementation code, sorting methods - ~70% complete
* helper_base: Add generic getField(..), getObjectRef<T>(..) and ↵Sven Gothel2020-05-042-29/+49
| | | | | | | | | setObjectRef<T>(..) Generic getField(..) allows convenient fetching of object fields. getObjectRef<T>(..) and setObjectRef<T>(..) allows get/set of jlong references of any user type pointer (reference).
* JNIGlobalRef: Add copy-ctor, move-ctor and equality operationsSven Gothel2020-05-042-3/+68
| | | | | | | | | | | | | | | | | | | | Goal is to utilize JNIGlobalRef as a first class shared jobject instance without the need to use std::shared_ptr<JNIGlobalRef>. +++ Efficiency and functionality: Add copy-ctor, move-ctor - Allow a JNIGlobalRef to be shared by creating copies with a 'NewGlobalRef(..)'. - Allow JNIGlobalRef to be moved by 'nulling' the source jobject +++ Functionality: Add equality operations - test inner jobject via 'IsSameObject(..)'
* Java: Utilize new EIRDataType in BluetoothDeviceStatusListener; ...Sven Gothel2020-05-031-4/+34
| | | | | | | | | | | | | | | | - Utilize new EIRDataType in BluetoothDeviceStatusListener - DBTAdapter: Add null checks for java callbacks - JNI: Adapt to EIRDataType on deviceUpdated. - DBTManager: Implement getAdapterListImpl(), supporting multiple adapter. JNI Implementation uses proper lambda for java-object ctor and fixed 'convert_vector_to_jobject'. - JNI: Fix 'convert_vector_to_jobject' using vector<unique_ptr<..>> as we cannot escape from a shared_ptr, i.e. no release() of ownership. - Working ScannerTinyB01 (discovery + connect using multiple adapter)
* JNI helper_base: Robustness: Add java_exception_check_and_throw(..) and use ↵Sven Gothel2020-05-022-13/+19
| | | | InternalError for exceptions.
* Refine: DBT API, HCISession/DBTAdapter lifecycle, API doc and C++ and ↵Sven Gothel2020-04-212-22/+88
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | C++/JNI exception handling - HCISession: Handle multiple connections - DBTDevice holds le_conn_handle and provides the le_disconnect as well - HCISession maintains a vector of connected devices - HCISession/DBTAdapter: Cleanup shutdown and refine lifecycle - DBTDevice/DBTAdapter: Drop explicit HCISession argument, simply use the attached HCISession of DBTAdapter. - Refine API doc in general +++ - Device Java/JNI: Add a few more methods to test connect/disconnect. +++ Refine C++ and C++/JNI exception handling: - Use new java_exception_check_and_throw(..): Throw C++ exception on pending Java exception, retrieving toString() message. - Use new java_exception_check(..): Return immediately from JNI on pending Java exception. - Replace macro CATCH_EXCEPTION_AND_RAISE_JAVA(..) with new rethrow_and_raise_java_exception(..), re-trhowing exception and raising detailed Java exception.
* Direct-BT: Fix BluetoothFactory; Renaming using 'DBT' prefixSven Gothel2020-04-201-1/+1
| | | | | - Fix BluetoothFactory: Group implementation identification (class-name and lib-names) - Renaming using 'DBT' prefix, easing identification (java and HCITypes -> DBTTypes)
* Initial working Java binding for the direct_bt C++ moduleSven Gothel2020-04-2024-3944/+341
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Example ScannerTinyB01 demonstrates efficient scanning devices using the new BluetoothDeviceDiscoveryListener interface. The C++ HCIObject extends JavaUplink, which handles the java object references via 'std::shared_ptr<JavaAnonObj>', where JavaAnonObj relies on later polymorph specialization. JavaAnonObj gets derived in the java/jni of direct_bt, where the JNI header + libraries are available. +++ The java inplementing NativeDownlink implementations store the nativeInstance to the C++ instances as well as handle their java references within the native instances. The C++ JavaUplink and Java NativeDownlink interfaces are complete the cross referencing java <-> native. +++ Native libraries are now split into pairs: - tinyb + javatinyb - direct_bt + javadirect_bt TODO: BluetoothFactory must chose the proper bundle! +++ The Java Adapter received a BluetoothDeviceDiscoveryListener hook, matching C++ Adapter's HCIDeviceDiscoveryListener. Since the Java Adapter implements its own discovery thread, using the BluetoothDeviceDiscoveryListener is more efficient then polling over a list of Devices fetched. ++++ TODO: Align Java and C++ class names, foremost in the C++ direct_bt space. TODO: Bind the whole C++ GATT functionality More testing.
* Implement direct_bt: Direct Bluetooth access via Linux's Kernel BlueZ ↵Sven Gothel2020-04-098-14/+14
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | protocol stack w/o D-Bus bluetoothd. By dropping BlueZ userspace D-Bus bluetoothd, we target high-performance reliable bluetooth support with least dependencies for embedded device configurations. See COPYING, describing which Linux Kernel BlueZ IOCTL information has been included in the source tree. We claim Linus Torvalds's Linux Kernel license exception regarding kernel syscalls (ioctl): <https://github.com/torvalds/linux/blob/master/LICENSES/exceptions/Linux-syscall-note> and hence maintain this project's license. The new direct_bt feature set is organized as follows - include/cppunit - api/direct_bt - api/ieee11073 - src/direct_bt - examples/direct_bt Note that the C++ direct_bt layer is not backward compatible to tinyb. Since the Java layer still needs to be completed, it has to be seen whether we will achieve compatibility or drop the original D-Bus tinyb module altogether in favor of a more streamlined API and implementation. Current state allows scanning for LE devices, connecting and parsing GATT plus receiving notifications and indications. See examples/direct_bt_scanner/dbt_scanner.cpp. The Linux Kernel BlueZ is configured via module MgmtComm.[hpp/cpp]
* C++ tinyb_hci: Working HCIScanner showing AD packets, demonstrating new HCI ↵Sven Gothel2020-02-159-0/+322
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | classes See scripts/build-x86_64.sh and README.md for build instructions. See scripts/run-hci_scanner.sh to start new HCI scanner (C++). New HCI C++ implementation redised in libtinyb_hci.so w/o GLIB/DBus dependencies, but 'libbluetooth.so' dependency. Following Class datastructures are complete: - HCIUtil: Exception types, uint128_t and endian conversions - General UUID interface and its efficient UUID16, UUID32 + UUID128 implementation. Conversion 'toString', respecting endianess, and UUID* -> UUID128 conversion. Requird member comparison operations due to using interface - HCIAdapter with its opened HCISession, as well as its discovered HCIDevices - HCIAdapter discover includes multiple advertising AD records, up to 25 according to the spec. - HCIAdapter parses full AD segment. TODO: Handle few more AD types API/Impl Details: - Datastructures utilize 'vector<shares_ptr<T>>' collections. - Most attributes are specified 'const' -> immutable for efficancy - Convenient collection access member operations - etc .. RESULTS: - Fast AD scanning of multiple devices w/ UUID and RSSI - Proper integration into tinyb project TODO: - Handle few more AD types - Test multiple parallel HCISession from HCIAdapter - HCIAdapter.connect() - Represent GATT Service, Characteristics and Description - Catch up with the Java binding step by step
* java jni: Split helper to helper_base + helper_tinyb (modularization)Sven Gothel2020-02-0912-89/+217
| | | | | | | C++ tinyb namespace is for the original D-Bus implementation, hence providing the base helper explicitly. Add static BluetoothFactory.getNativeAPIVersion().
* Java Refactory: Expose intefaces via org.tinyb.* and D-Bus implementation ↵Sven Gothel2020-02-099-153/+153
| | | | | | | | | | | | | | | | | | | | via tinyb.dbus.* Allows alternative interface implementations and delegated wrapper implementations for debug and tracing. Original D-Bus implementation can be retrieved via: 'BluetoothManager org.tinyb.BluetoothFactory.getDBusBluetoothManager()' A HCI native implementation will follow up w/o use of D-Bus. Otherwise the Java API is unchanged and this tinyb can replace previous implementation. Due to the major API change (despite interface is unchanged but BluetoothManager instantiation) the jar file has been renamed from 'tinyb.jar' to 'tinyb2.jar'. Same goes with the version number, which will be set to 'v2.0.0'. The native library and class definition is kept unchanged using the original names so far.
* Move java classes to package sub folderSven Gothel2020-02-0815-1730/+0
|
* JNIEnvContainer::attach/detach: Reuse GetEnv's JNIEnv if available and don't ↵Sven Gothel2020-02-052-6/+27
| | | | detach GetEnv's JNIEnv.
* Add experimental Adapter1::ConnectDevice callSven Gothel2020-02-052-1/+66
| | | | See https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/adapter-api.txt
* BluetoothManager: Expose native get_default_adapter() to java as ↵Sven Gothel2020-02-012-0/+38
| | | | | | | getDefaultAdapter() Notable: manager.stopDiscovery() doesn't lead to !manager.getDiscovering() (radio silence). Only manager.getDefaultAdapter().stopDiscovery() worked.
* BluetoothManager: Allow launching from plain class files (no jar package w/ ↵Sven Gothel2020-01-311-1/+1
| | | | version number)
* Expose BluetoothDevice async calls: device1_call_connect(..) / ↵Sven Gothel2020-01-312-0/+49
| | | | device1_call_connect_finish via Java connectAsyncStart() and connectAsyncFinish()
* Implementing discovery filter by UUIDs (java) and fixing a bug with ↵Vlad Kolotov2017-10-142-4/+21
| | | | | | discovery filter by UUIDs (c++) Signed-off-by: Vlad Kolotov <[email protected]>
* Implementing a generic method to set discover filters (UUIDs, rssi, ↵Vlad Kolotov2017-10-145-4/+78
| | | | | | pathloss, type) Signed-off-by: Vlad Kolotov <[email protected]>
* Adding support for setting RSSI discovery filterVlad Kolotov2017-10-142-1/+28
| | | | Signed-off-by: Vlad Kolotov <[email protected]>