diff --git a/README.md b/README.md index f7b470cb..77cf53f2 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ The library will remain open source and MIT licensed and can still be used, fork - Updated minimum required Java version to 17 - Improved handling of listening connections to allow proper bootstrapping the connection before actually starting accepting new connections (thanks to [brett-smith](https://github.com/brett-smith) ([#213](https://github.com/hypfvieh/dbus-java/issues/213))) - Updated export-object documentation ([#236](https://github.com/hypfvieh/dbus-java/issues/236)) + - Fixed issues with autoConnect option, added method to register to bus by 'Hello' message manually, thanks to [brett-smith](https://github.com/brett-smith) ([#238](https://github.com/hypfvieh/dbus-java/issues/238)) ##### Changes in 4.3.1 (2023-10-03): - Provide classloader to ServiceLoader in TransportBuilder (for loading actual transports) and AbstractTransport (for loading IMessageReader/Writer implementations), thanks to [cthbleachbit](https://github.com/cthbleachbit) ([#210](https://github.com/hypfvieh/dbus-java/issues/210), [PR#211](https://github.com/hypfvieh/dbus-java/issues/211)) diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/FileDescriptor.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/FileDescriptor.java index 7f329d09..e8aef9d0 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/FileDescriptor.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/FileDescriptor.java @@ -1,20 +1,17 @@ package org.freedesktop.dbus; import org.freedesktop.dbus.exceptions.MarshallingException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.freedesktop.dbus.spi.message.ISocketProvider; +import org.freedesktop.dbus.utils.ReflectionFileDescriptorHelper; -import java.lang.reflect.*; +import java.util.Optional; /** - * Represents a FileDescriptor to be passed over the bus. Can be created from - * either an integer(gotten through some JNI/JNA/JNR call) or from a - * java.io.FileDescriptor. - * + * Represents a FileDescriptor to be passed over the bus.
+ * Can be created from either an integer (gotten through some JNI/JNA/JNR call) or from a + * {@link java.io.FileDescriptor}. */ -public class FileDescriptor { - - private final Logger logger = LoggerFactory.getLogger(getClass()); +public final class FileDescriptor { private final int fd; @@ -22,39 +19,76 @@ public FileDescriptor(int _fd) { fd = _fd; } - public FileDescriptor(java.io.FileDescriptor _data) throws MarshallingException { - fd = getFileDescriptor(_data); - } + /** + * Converts this DBus {@link FileDescriptor} to a {@link java.io.FileDescriptor}.
+ * Tries to use the provided ISocketProvider if present first.
+ * If not present or conversion failed, tries to convert using reflection. + * + * @param _provider provider or null + * + * @return java file descriptor + * @throws MarshallingException when converting fails + */ + public java.io.FileDescriptor toJavaFileDescriptor(ISocketProvider _provider) throws MarshallingException { + if (_provider != null) { + Optional result = _provider.createFileDescriptor(fd); + if (result.isPresent()) { + return result.get(); + } + } - public java.io.FileDescriptor toJavaFileDescriptor() throws MarshallingException { - return createFileDescriptorByReflection(fd); + return ReflectionFileDescriptorHelper.getInstance() + .flatMap(helper -> helper.createFileDescriptor(fd)) + .orElseThrow(() -> new MarshallingException("Could not create new FileDescriptor instance")); } public int getIntFileDescriptor() { return fd; } - private int getFileDescriptor(java.io.FileDescriptor _data) throws MarshallingException { - Field declaredField; - try { - declaredField = _data.getClass().getDeclaredField("fd"); - declaredField.setAccessible(true); - return declaredField.getInt(_data); - } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException _ex) { - logger.error("Could not get filedescriptor by reflection.", _ex); - throw new MarshallingException("Could not get member 'fd' of FileDescriptor by reflection!", _ex); + @Override + public boolean equals(Object _o) { + if (this == _o) { + return true; } + if (_o == null || getClass() != _o.getClass()) { + return false; + } + FileDescriptor that = (FileDescriptor) _o; + return fd == that.fd; + } + + @Override + public int hashCode() { + return fd; } - private java.io.FileDescriptor createFileDescriptorByReflection(long _demarshallint) throws MarshallingException { - try { - Constructor constructor = java.io.FileDescriptor.class.getDeclaredConstructor(int.class); - constructor.setAccessible(true); - return constructor.newInstance((int) _demarshallint); - } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException - | IllegalArgumentException | InvocationTargetException _ex) { - logger.error("Could not create new FileDescriptor instance by reflection.", _ex); - throw new MarshallingException("Could not create new FileDescriptor instance by reflection", _ex); + @Override + public String toString() { + return FileDescriptor.class.getSimpleName() + "[fd=" + fd + "]"; + } + + /** + * Utility method to create a DBus {@link FileDescriptor} from a {@link java.io.FileDescriptor}.
+ * Tries to use the provided ISocketProvider if present first.
+ * If not present or conversion failed, tries to convert using reflection. + * + * @param _data file descriptor + * @param _provider socket provider or null + * + * @return DBus FileDescriptor + * @throws MarshallingException when conversion fails + */ + public static FileDescriptor fromJavaFileDescriptor(java.io.FileDescriptor _data, ISocketProvider _provider) throws MarshallingException { + if (_provider != null) { + Optional result = _provider.getFileDescriptorValue(_data); + if (result.isPresent()) { + return new FileDescriptor(result.get()); + } } + + return new FileDescriptor(ReflectionFileDescriptorHelper.getInstance() + .flatMap(helper -> helper.getFileDescriptorValue(_data)) + .orElseThrow(() -> new MarshallingException("Could not get FileDescriptor value"))); } } diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/AbstractConnection.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/AbstractConnection.java index f9c62475..38ce6698 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/AbstractConnection.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/AbstractConnection.java @@ -1445,6 +1445,10 @@ public TransportConfig getTransportConfig() { return transport.getTransportConfig(); } + public boolean isFileDescriptorSupported() { + return transport.isFileDescriptorSupported(); + } + @Override public String toString() { return getClass().getSimpleName() + "[address=" + busAddress + "]"; diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/config/TransportConfig.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/config/TransportConfig.java index 60762831..ed03cc80 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/config/TransportConfig.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/config/TransportConfig.java @@ -32,6 +32,7 @@ public final class TransportConfig { private String fileGroup; private byte endianess = BaseConnectionBuilder.getSystemEndianness(); + private boolean registerSelf = true; /** * Unix file permissions to set on socket file if this is a server transport (ignored on Windows, does nothing if @@ -151,6 +152,14 @@ public void setEndianess(byte _endianess) { endianess = _endianess; } + public boolean isRegisterSelf() { + return registerSelf; + } + + public void setRegisterSelf(boolean _registerSelf) { + registerSelf = _registerSelf; + } + /** * Toggles the busaddress to be a listening (server) or non listening (client) connection. * @param _listening true to be a server connection diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/config/TransportConfigBuilder.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/config/TransportConfigBuilder.java index c202867b..974215d5 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/config/TransportConfigBuilder.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/config/TransportConfigBuilder.java @@ -102,6 +102,19 @@ public X withAutoConnect(boolean _connect) { return self(); } + /** + * Register the new connection on DBus using 'hello' message. Default is true. + * + * @param _register boolean + * @return this + * + * @since 5.0.0 - 2023-10-11 + */ + public X withRegisterSelf(boolean _register) { + config.setRegisterSelf(_register); + return self(); + } + /** * Switch to the {@link SaslConfigBuilder} to configure the SASL authentication mechanism.
* Use {@link SaslConfigBuilder#back()} to return to this builder when finished. diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/impl/DBusConnection.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/impl/DBusConnection.java index c39c8817..f297ec29 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/impl/DBusConnection.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/impl/DBusConnection.java @@ -47,6 +47,9 @@ public final class DBusConnection extends AbstractConnection { private final String machineId; private DBus dbus; + /** Whether the connection was registered using 'Hello' message. */ + private boolean registered; + /** Count how many 'connections' we manage internally. * This is required because a {@link DBusConnection} to the same address will always return the same object and * the 'real' disconnection should only occur when there is no second/third/whatever connection is left. */ @@ -73,10 +76,9 @@ private AtomicInteger getConcurrentConnections() { /** * Connect to bus and register if asked. Should only be called by Builder. * - * @param _registerSelf true to register * @throws DBusException if registering or connection fails */ - void connect(boolean _registerSelf) throws DBusException { + void connectImpl() throws DBusException { // start listening for calls try { listen(); @@ -89,14 +91,32 @@ void connect(boolean _registerSelf) throws DBusException { addSigHandlerWithoutMatch(DBus.NameAcquired.class, h); // register ourselves if not disabled - if (_registerSelf) { - dbus = getRemoteObject("org.freedesktop.DBus", "/org/freedesktop/DBus", DBus.class); - try { - busnames.add(dbus.Hello()); - } catch (DBusExecutionException _ex) { - logger.debug("Error while doing 'Hello' handshake", _ex); - throw new DBusException(_ex.getMessage()); - } + if (getTransportConfig().isRegisterSelf() && getTransport().isConnected()) { + register(); + } + } + + /** + * Register this connection on the bus using 'Hello' message.
+ * Will do nothing if session was already registered. + * + * @throws DBusException when sending message fails + * + * @since 5.0.0 - 2023-10-11 + */ + public void register() throws DBusException { + if (registered) { + return; + } + + dbus = getRemoteObject("org.freedesktop.DBus", "/org/freedesktop/DBus", DBus.class); + + try { + busnames.add(dbus.Hello()); + registered = true; + } catch (DBusExecutionException _ex) { + logger.debug("Error while doing 'Hello' handshake", _ex); + throw new DBusException(_ex.getMessage(), _ex); } } diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/impl/DBusConnectionBuilder.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/impl/DBusConnectionBuilder.java index 72905e56..3d30e946 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/impl/DBusConnectionBuilder.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/impl/DBusConnectionBuilder.java @@ -20,7 +20,6 @@ public final class DBusConnectionBuilder extends BaseConnectionBuilder { private final String machineId; - private boolean registerSelf = true; private boolean shared = true; private DBusConnectionBuilder(BusAddress _address, String _machineId) { @@ -146,16 +145,6 @@ private static BusAddress validateTransportAddress(BusAddress _address) { return address; } - /** - * Register the new connection on DBus using 'hello' message. Default is true. - * - * @param _register boolean - * @return this - */ - public DBusConnectionBuilder withRegisterSelf(boolean _register) { - registerSelf = _register; - return this; - } /** * Use this connection as shared connection. Shared connection means that the same connection is used multiple times @@ -199,7 +188,7 @@ public DBusConnection build() throws DBusException { c.setDisconnectCallback(getDisconnectCallback()); c.setWeakReferences(isWeakReference()); - c.connect(registerSelf); + c.connectImpl(); return c; } diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/transports/AbstractTransport.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/transports/AbstractTransport.java index 7afaec69..ca441e39 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/transports/AbstractTransport.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/transports/AbstractTransport.java @@ -235,6 +235,7 @@ private void authenticate(SocketChannel _sock) throws IOException { private TransportConnection createInputOutput(SocketChannel _socket) { IMessageReader reader = null; IMessageWriter writer = null; + ISocketProvider providerImpl = null; try { for (ISocketProvider provider : spiLoader) { logger.debug("Found ISocketProvider {}", provider); @@ -244,6 +245,7 @@ private TransportConnection createInputOutput(SocketChannel _socket) { writer = provider.createWriter(_socket); if (reader != null && writer != null) { logger.debug("Using ISocketProvider {}", provider); + providerImpl = provider; break; } } @@ -261,7 +263,7 @@ private TransportConnection createInputOutput(SocketChannel _socket) { // allows it } - return new TransportConnection(messageFactory, _socket, writer, reader); + return new TransportConnection(messageFactory, _socket, providerImpl, writer, reader); } /** @@ -313,6 +315,10 @@ public TransportConfig getTransportConfig() { return config; } + public boolean isFileDescriptorSupported() { + return fileDescriptorSupported; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(getClass().getSimpleName()); diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/transports/TransportConnection.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/transports/TransportConnection.java index 6b0ed378..88945b6a 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/transports/TransportConnection.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/transports/TransportConnection.java @@ -1,8 +1,7 @@ package org.freedesktop.dbus.connections.transports; import org.freedesktop.dbus.messages.MessageFactory; -import org.freedesktop.dbus.spi.message.IMessageReader; -import org.freedesktop.dbus.spi.message.IMessageWriter; +import org.freedesktop.dbus.spi.message.*; import java.io.Closeable; import java.io.IOException; @@ -27,12 +26,14 @@ public class TransportConnection implements Closeable { private final SocketChannel channel; private final IMessageWriter writer; private final IMessageReader reader; + private final ISocketProvider socketProviderImpl; private final MessageFactory messageFactory; - public TransportConnection(MessageFactory _factory, SocketChannel _channel, IMessageWriter _writer, IMessageReader _reader) { + public TransportConnection(MessageFactory _factory, SocketChannel _channel, ISocketProvider _socketProviderImpl, IMessageWriter _writer, IMessageReader _reader) { messageFactory = _factory; channel = _channel; + socketProviderImpl = _socketProviderImpl; writer = _writer; reader = _reader; } @@ -49,6 +50,10 @@ public IMessageReader getReader() { return reader; } + public ISocketProvider getSocketProviderImpl() { + return socketProviderImpl; + } + public long getId() { return id; } diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/AbstractInputStreamMessageReader.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/AbstractInputStreamMessageReader.java index 9632ab68..91779c6c 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/AbstractInputStreamMessageReader.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/AbstractInputStreamMessageReader.java @@ -27,13 +27,14 @@ public abstract class AbstractInputStreamMessageReader implements IMessageReader private final byte[] buf; private final byte[] tbuf; private final SocketChannel inputChannel; - private final boolean hasFileDescriptorSupport; private byte[] header; private byte[] body; - public AbstractInputStreamMessageReader(final SocketChannel _in, boolean _hasFileDescriptorSupport) { - hasFileDescriptorSupport = _hasFileDescriptorSupport; + private final ISocketProvider socketProviderImpl; + + public AbstractInputStreamMessageReader(final SocketChannel _in, ISocketProvider _socketProviderImpl) { + socketProviderImpl = Objects.requireNonNull(_socketProviderImpl, "ISocketProvider implementation required"); inputChannel = Objects.requireNonNull(_in, "SocketChannel required"); len = new int[4]; tbuf = new byte[4]; @@ -168,7 +169,7 @@ public final Message readMessage() throws IOException, DBusException { try { List fds = null; - if (hasFileDescriptorSupport) { + if (socketProviderImpl.isFileDescriptorPassingSupported()) { fds = readFileDescriptors(inputChannel); } @@ -203,6 +204,14 @@ public final Message readMessage() throws IOException, DBusException { */ protected abstract List readFileDescriptors(SocketChannel _inputChannel) throws DBusException; + protected Logger getLogger() { + return logger; + } + + protected ISocketProvider getSocketProviderImpl() { + return socketProviderImpl; + } + @Override public void close() throws IOException { if (inputChannel.isOpen()) { @@ -218,7 +227,7 @@ public boolean isClosed() { @Override public String toString() { - return getClass().getSimpleName() + " [inputChannel=" + inputChannel + ", hasFileDescriptorSupport=" + hasFileDescriptorSupport + "]"; + return getClass().getSimpleName() + " [inputChannel=" + inputChannel + ", socketProviderImpl=" + socketProviderImpl + "]"; } } diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/AbstractOutputStreamMessageWriter.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/AbstractOutputStreamMessageWriter.java index 8e6424a3..f8aae728 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/AbstractOutputStreamMessageWriter.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/AbstractOutputStreamMessageWriter.java @@ -22,11 +22,12 @@ public abstract class AbstractOutputStreamMessageWriter implements IMessageWrite private final Logger logger = LoggerFactory.getLogger(getClass()); private final SocketChannel outputChannel; - private final boolean hasFileDescriptorSupport; - public AbstractOutputStreamMessageWriter(final SocketChannel _out, boolean _fileDescriptorSupport) { + private final ISocketProvider socketProviderImpl; + + public AbstractOutputStreamMessageWriter(final SocketChannel _out, ISocketProvider _socketProviderImpl) { outputChannel = Objects.requireNonNull(_out, "SocketChannel required"); - hasFileDescriptorSupport = _fileDescriptorSupport; + socketProviderImpl = Objects.requireNonNull(_socketProviderImpl, "ISocketProvider implementation required"); } @Override @@ -40,6 +41,10 @@ public final void writeMessage(Message _msg) throws IOException { return; } + if (socketProviderImpl.isFileDescriptorPassingSupported()) { + writeFileDescriptors(outputChannel, _msg.getFiledescriptors()); + } + for (byte[] buf : _msg.getWireData()) { if (logger.isTraceEnabled()) { logger.trace("{}", null == buf ? "(buffer was null)" : Hexdump.format(buf)); @@ -51,10 +56,6 @@ public final void writeMessage(Message _msg) throws IOException { outputChannel.write(ByteBuffer.wrap(buf)); } - if (hasFileDescriptorSupport) { - writeFileDescriptors(outputChannel, _msg.getFiledescriptors()); - } - logger.trace("Message sent: {}", _msg); } @@ -69,6 +70,14 @@ public final void writeMessage(Message _msg) throws IOException { */ protected abstract void writeFileDescriptors(SocketChannel _outputChannel, List _filedescriptors) throws IOException; + protected Logger getLogger() { + return logger; + } + + protected ISocketProvider getSocketProviderImpl() { + return socketProviderImpl; + } + @Override public void close() throws IOException { logger.debug("Closing Message Writer"); @@ -85,7 +94,7 @@ public boolean isClosed() { @Override public String toString() { - return getClass().getSimpleName() + " [outputChannel=" + outputChannel + ", hasFileDescriptorSupport=" + hasFileDescriptorSupport + "]"; + return getClass().getSimpleName() + " [outputChannel=" + outputChannel + ", socketProviderImpl=" + socketProviderImpl + "]"; } } diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/DefaultSocketProvider.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/DefaultSocketProvider.java new file mode 100644 index 00000000..5072c1da --- /dev/null +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/DefaultSocketProvider.java @@ -0,0 +1,40 @@ +package org.freedesktop.dbus.spi.message; + +import java.io.IOException; +import java.nio.channels.SocketChannel; + +/** + * Default internally used socket provider implementation. + * + * @author hypfvieh + * @since 5.0.0 - 2023-10-09 + */ +final class DefaultSocketProvider implements ISocketProvider { + + static final ISocketProvider INSTANCE = new DefaultSocketProvider(); + + private DefaultSocketProvider() { + + } + + @Override + public IMessageReader createReader(SocketChannel _socket) throws IOException { + return new InputStreamMessageReader(_socket); + } + + @Override + public IMessageWriter createWriter(SocketChannel _socket) throws IOException { + return new OutputStreamMessageWriter(_socket); + } + + @Override + public void setFileDescriptorSupport(boolean _support) { + // not supported + } + + @Override + public boolean isFileDescriptorPassingSupported() { + return false; + } + +} diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/ISocketProvider.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/ISocketProvider.java index 0215bf21..5359a011 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/ISocketProvider.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/ISocketProvider.java @@ -2,8 +2,10 @@ import org.freedesktop.dbus.connections.transports.AbstractTransport; +import java.io.FileDescriptor; import java.io.IOException; import java.nio.channels.SocketChannel; +import java.util.Optional; public interface ISocketProvider { /** @@ -39,4 +41,30 @@ public interface ISocketProvider { * @return true if file descriptors are supported by this provider, false otherwise */ boolean isFileDescriptorPassingSupported(); + + /** + * Attempts to extract raw FileDescriptor value from {@link FileDescriptor} instance. + * Note that not any {@link FileDescriptor} can be represented as int, for example Windows uses HANDLE as descriptor, + * which excess range of int values, thus cannot be safely cast to int. + * + * @param _fd FileDescriptor to extract value from + * @return int representation, packed to {@link Optional} if operation succeeded, or {@link Optional#empty()} otherwise + * @see #createFileDescriptor(int) + * @since 5.0.0 - 2023-10-07 + */ + default Optional getFileDescriptorValue(FileDescriptor _fd) { + return Optional.empty(); + } + + /** + * Attempts to create native {@link FileDescriptor} from raw int value. + * + * @param _fd FileDescriptor to extract value from + * @return {@link FileDescriptor}, instantiated with provided value, packed to {@link Optional} if operation succeeded, or {@link Optional#empty()} otherwise + * @see #getFileDescriptorValue(FileDescriptor) + * @since 5.0.0 - 2023-10-07 + */ + default Optional createFileDescriptor(int _fd) { + return Optional.empty(); + } } diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/InputStreamMessageReader.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/InputStreamMessageReader.java index 9ab356df..3446c269 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/InputStreamMessageReader.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/InputStreamMessageReader.java @@ -8,7 +8,7 @@ public class InputStreamMessageReader extends AbstractInputStreamMessageReader { public InputStreamMessageReader(final SocketChannel _in) { - super(_in, false); + super(_in, DefaultSocketProvider.INSTANCE); } @Override diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/OutputStreamMessageWriter.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/OutputStreamMessageWriter.java index 9fa768b2..be0440a1 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/OutputStreamMessageWriter.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/spi/message/OutputStreamMessageWriter.java @@ -8,7 +8,7 @@ public class OutputStreamMessageWriter extends AbstractOutputStreamMessageWriter { public OutputStreamMessageWriter(SocketChannel _out) { - super(_out, false); + super(_out, DefaultSocketProvider.INSTANCE); } @Override diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/utils/ReflectionFileDescriptorHelper.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/utils/ReflectionFileDescriptorHelper.java new file mode 100644 index 00000000..3f90cb32 --- /dev/null +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/utils/ReflectionFileDescriptorHelper.java @@ -0,0 +1,78 @@ +package org.freedesktop.dbus.utils; + +import org.freedesktop.dbus.spi.message.ISocketProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.FileDescriptor; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.Optional; + +/** + * Helper to work with {@link FileDescriptor} instances by using reflection + * + * @author Sergey Shatunov + * @since 5.0.0 - 2023-10-07 + */ +public final class ReflectionFileDescriptorHelper { + private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionFileDescriptorHelper.class); + private static volatile ReflectionFileDescriptorHelper instance; + + private final Field fdField; + private final Constructor constructor; + + private ReflectionFileDescriptorHelper() throws ReflectiveOperationException { + fdField = FileDescriptor.class.getDeclaredField("fd"); + fdField.setAccessible(true); + constructor = FileDescriptor.class.getDeclaredConstructor(int.class); + constructor.setAccessible(true); + } + + /** + * @return {@link ReflectionFileDescriptorHelper} instance, or {@link Optional#empty()} if it cannot be initialized + * (mainly due to missing reflection access) + */ + public static Optional getInstance() { + if (instance == null) { + synchronized (ReflectionFileDescriptorHelper.class) { + if (instance == null) { + try { + instance = new ReflectionFileDescriptorHelper(); + } catch (ReflectiveOperationException _ex) { + LOGGER.error("Unable to hook up java.io.FileDescriptor by using reflection.", _ex); + return Optional.empty(); + } + } + } + } + + return Optional.ofNullable(instance); + } + + /** + * @see ISocketProvider#getFileDescriptorValue(FileDescriptor) + */ + public Optional getFileDescriptorValue(FileDescriptor _fd) { + try { + return Optional.of(fdField.getInt(_fd)); + } catch (SecurityException | IllegalArgumentException | IllegalAccessException _ex) { + LOGGER.error("Could not get file descriptor by reflection.", _ex); + return Optional.empty(); + } + } + + /** + * @see ISocketProvider#createFileDescriptor(int) + */ + public Optional createFileDescriptor(int _fd) { + try { + return Optional.of(constructor.newInstance(_fd)); + } catch (SecurityException | InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException _ex) { + LOGGER.error("Could not create new FileDescriptor instance by reflection.", _ex); + return Optional.empty(); + } + } +} diff --git a/dbus-java-examples/src/main/java/com/github/hypfvieh/dbus/examples/pulseaudio/PulseAudioDbus.java b/dbus-java-examples/src/main/java/com/github/hypfvieh/dbus/examples/pulseaudio/PulseAudioDbus.java index fb401a78..fbaadba3 100755 --- a/dbus-java-examples/src/main/java/com/github/hypfvieh/dbus/examples/pulseaudio/PulseAudioDbus.java +++ b/dbus-java-examples/src/main/java/com/github/hypfvieh/dbus/examples/pulseaudio/PulseAudioDbus.java @@ -35,8 +35,10 @@ public static void main(String[] _args) throws DBusException { } else { DBusConnection connection = DBusConnectionBuilder .forAddress(address) - .withRegisterSelf(false) .withShared(false) + .transportConfig() + .withRegisterSelf(false) + .back() .build(); Properties core1Props = connection.getRemoteObject("org.PulseAudio.Core1", "/org/pulseaudio/core1", Properties.class); diff --git a/dbus-java-tests/src/test/java/org/freedesktop/dbus/test/FileDescriptorsTest.java b/dbus-java-tests/src/test/java/org/freedesktop/dbus/test/FileDescriptorsTest.java new file mode 100644 index 00000000..3389c1c8 --- /dev/null +++ b/dbus-java-tests/src/test/java/org/freedesktop/dbus/test/FileDescriptorsTest.java @@ -0,0 +1,86 @@ +package org.freedesktop.dbus.test; + +import org.freedesktop.dbus.FileDescriptor; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder; +import org.freedesktop.dbus.connections.transports.TransportBuilder; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.interfaces.DBusInterface; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; + +import java.io.IOException; +import java.util.stream.Stream; + +@EnabledIf(value = "isFileDescriptorSupported", disabledReason = "file descriptors not supported with the current transport") +public class FileDescriptorsTest extends AbstractDBusBaseTest { + public static final String TEST_OBJECT_PATH = "/FileDescriptorsTest"; + + private DBusConnection serverconn = null, clientconn = null; + + @BeforeEach + public void setUp() throws DBusException { + serverconn = DBusConnectionBuilder.forSessionBus().withShared(false).build(); + clientconn = DBusConnectionBuilder.forSessionBus().withShared(false).build(); + serverconn.requestBusName("foo.bar.Test"); + serverconn.exportObject(TEST_OBJECT_PATH, new FDPassingImpl()); + } + + @AfterEach + public void tearDown() throws Exception { + logger.debug("Checking for outstanding errors"); + DBusExecutionException dbee = serverconn.getError(); + if (null != dbee) { + throw dbee; + } + dbee = clientconn.getError(); + if (null != dbee) { + throw dbee; + } + + logger.debug("Disconnecting"); + /* Disconnect from the bus. */ + clientconn.disconnect(); + serverconn.releaseBusName("foo.bar.Test"); + serverconn.disconnect(); + } + + public static boolean isFileDescriptorSupported() throws DBusException, IOException { + if (!TransportBuilder.getRegisteredBusTypes().contains("UNIX")) { + return false; + } + try (DBusConnection conn = DBusConnectionBuilder.forSessionBus().build()) { + return conn.isFileDescriptorSupported(); + } + } + + @Test + public void fileDescriptorPassing() throws DBusException { + FDPassing remoteObject = clientconn.getRemoteObject("foo.bar.Test", TEST_OBJECT_PATH, FDPassing.class); + Stream.of(0, 1, 2).map(FileDescriptor::new).forEach(fd -> { + // that's not a mistake of using NotEquals here, as fd passing make a new copy with a new value + Assertions.assertNotEquals(fd.getIntFileDescriptor(), remoteObject.doNothing(fd).getIntFileDescriptor()); + }); + } + + public interface FDPassing extends DBusInterface { + FileDescriptor doNothing(FileDescriptor _fd); + } + + private static final class FDPassingImpl implements FDPassing { + + @Override + public String getObjectPath() { + return TEST_OBJECT_PATH; + } + + @Override + public FileDescriptor doNothing(FileDescriptor _fd) { + return _fd; + } + } +} diff --git a/dbus-java-transport-junixsocket/src/main/java/org/freedesktop/dbus/transport/junixsocket/JUnixSocketMessageReader.java b/dbus-java-transport-junixsocket/src/main/java/org/freedesktop/dbus/transport/junixsocket/JUnixSocketMessageReader.java index ed992f41..2b0326c2 100644 --- a/dbus-java-transport-junixsocket/src/main/java/org/freedesktop/dbus/transport/junixsocket/JUnixSocketMessageReader.java +++ b/dbus-java-transport-junixsocket/src/main/java/org/freedesktop/dbus/transport/junixsocket/JUnixSocketMessageReader.java @@ -2,22 +2,20 @@ import org.freedesktop.dbus.exceptions.DBusException; import org.freedesktop.dbus.spi.message.AbstractInputStreamMessageReader; +import org.freedesktop.dbus.spi.message.ISocketProvider; import org.newsclub.net.unix.AFUNIXSocketChannel; -import org.newsclub.net.unix.FileDescriptorCast; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.FileDescriptor; import java.io.IOException; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.List; +import java.util.Optional; public class JUnixSocketMessageReader extends AbstractInputStreamMessageReader { - private final Logger logger = LoggerFactory.getLogger(getClass()); - public JUnixSocketMessageReader(AFUNIXSocketChannel _socket, boolean _hasFileDescriptorSupport) { - super(_socket, _hasFileDescriptorSupport); + public JUnixSocketMessageReader(AFUNIXSocketChannel _socket, ISocketProvider _socketProviderImpl) { + super(_socket, _socketProviderImpl); } @Override @@ -30,10 +28,11 @@ protected List readFileDescriptors(SocketCh } else { List fds = new ArrayList<>(); for (FileDescriptor fd : receivedFileDescriptors) { - fds.add(new org.freedesktop.dbus.FileDescriptor(FileDescriptorCast.using(fd).as(Integer.class))); + Optional fileDescriptorValue = getSocketProviderImpl().getFileDescriptorValue(fd); + fileDescriptorValue.ifPresent(f -> fds.add(new org.freedesktop.dbus.FileDescriptor(f))); } - logger.debug("=> {}", fds); + getLogger().debug("=> {}", fds); return fds; } } catch (IOException _ex) { diff --git a/dbus-java-transport-junixsocket/src/main/java/org/freedesktop/dbus/transport/junixsocket/JUnixSocketMessageWriter.java b/dbus-java-transport-junixsocket/src/main/java/org/freedesktop/dbus/transport/junixsocket/JUnixSocketMessageWriter.java index dd2d54f0..a5604f4c 100644 --- a/dbus-java-transport-junixsocket/src/main/java/org/freedesktop/dbus/transport/junixsocket/JUnixSocketMessageWriter.java +++ b/dbus-java-transport-junixsocket/src/main/java/org/freedesktop/dbus/transport/junixsocket/JUnixSocketMessageWriter.java @@ -1,8 +1,9 @@ package org.freedesktop.dbus.transport.junixsocket; +import org.freedesktop.dbus.exceptions.MarshallingException; import org.freedesktop.dbus.spi.message.AbstractOutputStreamMessageWriter; +import org.freedesktop.dbus.spi.message.ISocketProvider; import org.newsclub.net.unix.AFUNIXSocketChannel; -import org.newsclub.net.unix.FileDescriptorCast; import java.io.FileDescriptor; import java.io.IOException; @@ -11,19 +12,23 @@ public class JUnixSocketMessageWriter extends AbstractOutputStreamMessageWriter { - public JUnixSocketMessageWriter(AFUNIXSocketChannel _socket, boolean _hasFileDescriptorSupport) { - super(_socket, _hasFileDescriptorSupport); + public JUnixSocketMessageWriter(AFUNIXSocketChannel _socket, ISocketProvider _socketProviderImpl) { + super(_socket, _socketProviderImpl); } @Override protected void writeFileDescriptors(SocketChannel _outputChannel, List _filedescriptors) throws IOException { if (_outputChannel instanceof AFUNIXSocketChannel) { if (_filedescriptors != null && !_filedescriptors.isEmpty()) { - FileDescriptor[] fds = new FileDescriptor[_filedescriptors.size()]; - for (int i = 0; i < _filedescriptors.size(); i++) { - fds[i] = FileDescriptorCast.unsafeUsing(_filedescriptors.get(i).getIntFileDescriptor()).getFileDescriptor(); + try { + FileDescriptor[] fds = new FileDescriptor[_filedescriptors.size()]; + for (int i = 0; i < _filedescriptors.size(); i++) { + fds[i] = _filedescriptors.get(i).toJavaFileDescriptor(getSocketProviderImpl()); + } + ((AFUNIXSocketChannel) _outputChannel).setOutboundFileDescriptors(fds); + } catch (MarshallingException _ex) { + throw new IOException("unable to marshall file descriptors", _ex); } - ((AFUNIXSocketChannel) _outputChannel).setOutboundFileDescriptors(fds); } else { ((AFUNIXSocketChannel) _outputChannel).setOutboundFileDescriptors((FileDescriptor[]) null); } diff --git a/dbus-java-transport-junixsocket/src/main/java/org/freedesktop/dbus/transport/junixsocket/JUnixSocketSocketProvider.java b/dbus-java-transport-junixsocket/src/main/java/org/freedesktop/dbus/transport/junixsocket/JUnixSocketSocketProvider.java index 56230706..38b72eca 100644 --- a/dbus-java-transport-junixsocket/src/main/java/org/freedesktop/dbus/transport/junixsocket/JUnixSocketSocketProvider.java +++ b/dbus-java-transport-junixsocket/src/main/java/org/freedesktop/dbus/transport/junixsocket/JUnixSocketSocketProvider.java @@ -1,16 +1,19 @@ package org.freedesktop.dbus.transport.junixsocket; -import org.freedesktop.dbus.spi.message.IMessageReader; -import org.freedesktop.dbus.spi.message.IMessageWriter; -import org.freedesktop.dbus.spi.message.ISocketProvider; -import org.newsclub.net.unix.AFSocket; -import org.newsclub.net.unix.AFSocketCapability; -import org.newsclub.net.unix.AFUNIXSocketChannel; +import org.freedesktop.dbus.spi.message.*; +import org.newsclub.net.unix.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.FileDescriptor; +import java.io.IOException; import java.nio.channels.SocketChannel; +import java.util.Optional; public class JUnixSocketSocketProvider implements ISocketProvider { - private boolean hasFileDescriptorSupport = false; + private static final Logger LOGGER = LoggerFactory.getLogger(JUnixSocketSocketProvider.class); + + private boolean hasFileDescriptorSupport = true; @Override public IMessageReader createReader(SocketChannel _socket) { @@ -18,7 +21,7 @@ public IMessageReader createReader(SocketChannel _socket) { return null; } if (_socket instanceof AFUNIXSocketChannel) { - return new JUnixSocketMessageReader((AFUNIXSocketChannel) _socket, hasFileDescriptorSupport); + return new JUnixSocketMessageReader((AFUNIXSocketChannel) _socket, this); } return null; } @@ -29,7 +32,7 @@ public IMessageWriter createWriter(SocketChannel _socket) { return null; } if (_socket instanceof AFUNIXSocketChannel) { - return new JUnixSocketMessageWriter((AFUNIXSocketChannel) _socket, hasFileDescriptorSupport); + return new JUnixSocketMessageWriter((AFUNIXSocketChannel) _socket, this); } return null; } @@ -41,6 +44,30 @@ public void setFileDescriptorSupport(boolean _support) { @Override public boolean isFileDescriptorPassingSupported() { - return AFSocket.supports(AFSocketCapability.CAPABILITY_FILE_DESCRIPTORS) && AFSocket.supports(AFSocketCapability.CAPABILITY_UNSAFE); + return hasFileDescriptorSupport && AFSocket.supports(AFSocketCapability.CAPABILITY_FILE_DESCRIPTORS) && AFSocket.supports(AFSocketCapability.CAPABILITY_UNSAFE); + } + + @Override + public Optional getFileDescriptorValue(FileDescriptor _fd) { + try { + return Optional.of(FileDescriptorCast.using(_fd).as(Integer.class)); + } catch (IOException | ClassCastException _ex) { + LOGGER.error("Could not get file descriptor by using junixsocket library", _ex); + return Optional.empty(); + } + } + + @Override + public Optional createFileDescriptor(int _fd) { + if (!AFSocket.supports(AFSocketCapability.CAPABILITY_UNSAFE)) { + LOGGER.debug("Could not create new FileDescriptor instance by using junixsocket library, as unsafe capabilities of that library is disabled."); + return Optional.empty(); + } + try { + return Optional.of(FileDescriptorCast.unsafeUsing(_fd).getFileDescriptor()); + } catch (IOException _ex) { + LOGGER.error("Could not create new FileDescriptor instance by using junixsocket library.", _ex); + return Optional.empty(); + } } }