From 8e246331f6f71320cfcc8defdd04e756a75f71cf Mon Sep 17 00:00:00 2001 From: Skyler Grey Date: Fri, 18 Aug 2023 13:30:35 +0000 Subject: [PATCH] Add a FunctionBasedURPConnection and a websocket URP connector MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FunctionBasedURPConnection is used to enable a client to open a URP connection to a fresh Kit instance in COOL. - This URP connector can be used with that and https://github.com/CollaboraOnline/online/pull/6992 to use a Java Uno Remote Protocol client over websockets - For interoperability with existing Collabora Online websockets a prefix (urp ) is added to each message sent and a similar prefix (urp: ) is expected on each message recieved. This allows sending over the same websocket as other data is being transmitted through. If you are writing a bridge to work with this, you will need to add/strip the prefixes accordingly - This commit uses Java WebSocket (https://github.com/TooTallNate/Java-WebSocket) to send data over websockets. Change-Id: I2bda3d0b988bef7883f9b6829eeb5b7ae8075f27 Signed-off-by: Skyler Grey Reviewed-on: https://gerrit.libreoffice.org/c/core/+/151171 Tested-by: Jenkins Reviewed-by: Caolán McNamara --- Makefile.fetch | 1 + RepositoryExternal.mk | 11 + configure.ac | 11 + desktop/qa/desktop_lib/test_desktop_lib.cxx | 4 +- desktop/source/lib/init.cxx | 227 ++++++ download.lst | 5 + external/Module_external.mk | 1 + .../ExternalPackage_java_websocket.mk | 16 + .../ExternalProject_java_websocket.mk | 31 + external/java_websocket/Makefile | 7 + .../java_websocket/Module_java_websocket.mk | 18 + external/java_websocket/README | 3 + .../UnpackedTarball_java_websocket.mk | 21 + .../java_websocket/patches/ant-build.patch | 26 + .../java_websocket/patches/no-slf4j.patch | 748 ++++++++++++++++++ include/LibreOfficeKit/LibreOfficeKit.h | 9 + include/LibreOfficeKit/LibreOfficeKit.hxx | 26 + include/sal/log-areas.dox | 1 + readlicense_oo/license/license.xml | 18 + ridljar/Jar_libreoffice.mk | 8 + .../com/sun/star/comp/helper/Bootstrap.java | 63 ++ .../websocket/ConnectionDescriptor.java | 60 ++ .../websocket/WebsocketConnection.java | 335 ++++++++ .../websocket/websocketConnector.java | 137 ++++ ridljar/source/libreoffice/module-info.java | 1 + ridljar/util/manifest | 3 + ure/source/README | 2 + 27 files changed, 1792 insertions(+), 1 deletion(-) create mode 100644 external/java_websocket/ExternalPackage_java_websocket.mk create mode 100644 external/java_websocket/ExternalProject_java_websocket.mk create mode 100644 external/java_websocket/Makefile create mode 100644 external/java_websocket/Module_java_websocket.mk create mode 100644 external/java_websocket/README create mode 100644 external/java_websocket/UnpackedTarball_java_websocket.mk create mode 100644 external/java_websocket/patches/ant-build.patch create mode 100644 external/java_websocket/patches/no-slf4j.patch create mode 100644 ridljar/com/sun/star/lib/connections/websocket/ConnectionDescriptor.java create mode 100644 ridljar/com/sun/star/lib/connections/websocket/WebsocketConnection.java create mode 100644 ridljar/com/sun/star/lib/connections/websocket/websocketConnector.java diff --git a/Makefile.fetch b/Makefile.fetch index 625e781dc36a..6640b0ade985 100644 --- a/Makefile.fetch +++ b/Makefile.fetch @@ -136,6 +136,7 @@ $(WORKDIR)/download: $(BUILDDIR)/config_$(gb_Side).mk $(SRCDIR)/download.lst $(S $(call fetch_Optional,HYPHEN,HYPHEN_TARBALL) \ $(call fetch_Optional,ICU,ICU_TARBALL) \ $(call fetch_Optional,ICU,ICU_DATA_TARBALL) \ + $(call fetch_Optional,JAVA_WEBSOCKET,JAVA_WEBSOCKET_TARBALL) \ $(call fetch_Optional,JFREEREPORT,JFREEREPORT_FLOW_ENGINE_TARBALL) \ $(call fetch_Optional,JFREEREPORT,JFREEREPORT_FLUTE_TARBALL) \ $(call fetch_Optional,JFREEREPORT,JFREEREPORT_LIBBASE_TARBALL) \ diff --git a/RepositoryExternal.mk b/RepositoryExternal.mk index 0d70c7e966fb..a7f6dc3897ec 100644 --- a/RepositoryExternal.mk +++ b/RepositoryExternal.mk @@ -3915,6 +3915,17 @@ endef endif # SYSTEM_JFREEREPORT +# no known distro packaged Java-Websocket at present + +ifeq ($(ENABLE_JAVA),TRUE) +$(eval $(call gb_Helper_register_jars_for_install,URE,ure, \ + java_websocket \ +)) +endif + +define gb_Jar__use_java_websocket +$(call gb_Jar_use_jar,$(1),java_websocket) +endef # Executables diff --git a/configure.ac b/configure.ac index 5ce8454546c4..5a682dde4d61 100644 --- a/configure.ac +++ b/configure.ac @@ -12690,6 +12690,17 @@ AC_SUBST(LIBASSUAN_LIBS) AC_SUBST(GPGMEPP_CFLAGS) AC_SUBST(GPGMEPP_LIBS) +AC_MSG_CHECKING([whether to build Java Websocket for the UNO remote websocket client]) +if test "$with_java" != "no"; then + AC_MSG_RESULT([yes]) + ENABLE_JAVA_WEBSOCKET=TRUE + BUILD_TYPE="$BUILD_TYPE JAVA_WEBSOCKET" +else + AC_MSG_RESULT([no]) + ENABLE_JAVA_WEBSOCKET= +fi +AC_SUBST(ENABLE_JAVA_WEBSOCKET) + AC_MSG_CHECKING([whether to build the Wiki Publisher extension]) if test "x$enable_ext_wiki_publisher" = "xyes" -a "x$enable_extension_integration" != "xno" -a "$with_java" != "no"; then AC_MSG_RESULT([yes]) diff --git a/desktop/qa/desktop_lib/test_desktop_lib.cxx b/desktop/qa/desktop_lib/test_desktop_lib.cxx index 35cbbfe60c15..8a55ae18172d 100644 --- a/desktop/qa/desktop_lib/test_desktop_lib.cxx +++ b/desktop/qa/desktop_lib/test_desktop_lib.cxx @@ -3681,10 +3681,12 @@ void DesktopLOKTest::testABI() CPPUNIT_ASSERT_EQUAL(classOffset(15), offsetof(struct _LibreOfficeKitClass, dumpState)); CPPUNIT_ASSERT_EQUAL(classOffset(16), offsetof(struct _LibreOfficeKitClass, extractRequest)); CPPUNIT_ASSERT_EQUAL(classOffset(17), offsetof(struct _LibreOfficeKitClass, trimMemory)); + CPPUNIT_ASSERT_EQUAL(classOffset(18), offsetof(struct _LibreOfficeKitClass, startURP)); + CPPUNIT_ASSERT_EQUAL(classOffset(19), offsetof(struct _LibreOfficeKitClass, stopURP)); // When extending LibreOfficeKit with a new function pointer, add new assert for the offsetof the // new function pointer and bump this assert for the size of the class. - CPPUNIT_ASSERT_EQUAL(classOffset(18), sizeof(struct _LibreOfficeKitClass)); + CPPUNIT_ASSERT_EQUAL(classOffset(20), sizeof(struct _LibreOfficeKitClass)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(0), offsetof(struct _LibreOfficeKitDocumentClass, destroy)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(1), offsetof(struct _LibreOfficeKitDocumentClass, saveAs)); diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx index 6db6f722bee6..4b81619cbc1f 100644 --- a/desktop/source/lib/init.cxx +++ b/desktop/source/lib/init.cxx @@ -49,6 +49,7 @@ #include #include #include +#include #include #include @@ -86,6 +87,7 @@ #include #include +#include #include #include #include @@ -108,6 +110,10 @@ #include #include #include +#include +#include +#include +#include #include #include @@ -121,6 +127,7 @@ #include #include #include +#include #include #ifdef IOS @@ -228,6 +235,9 @@ using namespace css; using namespace vcl; using namespace desktop; using namespace utl; +using namespace bridge; +using namespace uno; +using namespace lang; static LibLibreOffice_Impl *gImpl = nullptr; static bool lok_preinit_2_called = false; @@ -2567,6 +2577,13 @@ static char* lo_extractRequest(LibreOfficeKit* pThis, static void lo_trimMemory(LibreOfficeKit* pThis, int nTarget); +static int +lo_startURP(LibreOfficeKit* pThis, void* pReceiveURPFromLOContext, void** pSendURPToLOContext, + int (*fnReceiveURPFromLO)(void* pContext, const signed char* pBuffer, int nLen), + int (**pfnSendURPToLO)(void* pContext, const signed char* pBuffer, int nLen)); + +static void lo_stopURP(LibreOfficeKit* pThis, void* pSendURPToLOContext); + static void lo_runLoop(LibreOfficeKit* pThis, LibreOfficeKitPollCallback pPollCallback, LibreOfficeKitWakeCallback pWakeCallback, @@ -2609,6 +2626,8 @@ LibLibreOffice_Impl::LibLibreOffice_Impl() m_pOfficeClass->dumpState = lo_dumpState; m_pOfficeClass->extractRequest = lo_extractRequest; m_pOfficeClass->trimMemory = lo_trimMemory; + m_pOfficeClass->startURP = lo_startURP; + m_pOfficeClass->stopURP = lo_stopURP; gOfficeClass = m_pOfficeClass; } @@ -3160,6 +3179,214 @@ static void lo_trimMemory(LibreOfficeKit* /* pThis */, int nTarget) } } +namespace +{ +class FunctionBasedURPInstanceProvider + : public ::cppu::WeakImplHelper +{ +private: + css::uno::Reference m_rContext; + +public: + FunctionBasedURPInstanceProvider( + const css::uno::Reference& rxContext); + + // XInstanceProvider + virtual css::uno::Reference + SAL_CALL getInstance(const OUString& aName) override; +}; + +// InstanceProvider +FunctionBasedURPInstanceProvider::FunctionBasedURPInstanceProvider( + const Reference& rxContext) + : m_rContext(rxContext) +{ +} + +Reference FunctionBasedURPInstanceProvider::getInstance(const OUString& aName) +{ + Reference rInstance; + + if (aName == "StarOffice.ServiceManager") + { + rInstance.set(m_rContext->getServiceManager()); + } + else if (aName == "StarOffice.ComponentContext") + { + rInstance = m_rContext; + } + else if (aName == "StarOffice.NamingService") + { + Reference rNamingService( + m_rContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.uno.NamingService", m_rContext), + UNO_QUERY); + if (rNamingService.is()) + { + rNamingService->registerObject("StarOffice.ServiceManager", + m_rContext->getServiceManager()); + rNamingService->registerObject("StarOffice.ComponentContext", m_rContext); + rInstance = rNamingService; + } + } + return rInstance; +} + +class FunctionBasedURPConnection : public cppu::WeakImplHelper +{ +public: + explicit FunctionBasedURPConnection(void*, int (*)(void* pContext, const signed char* pBuffer, + int nLen)); + ~FunctionBasedURPConnection(); + + // These overridden member functions use "read" and "write" from the point of view of LO, + // i.e. the opposite to how startURP() uses them. + virtual sal_Int32 SAL_CALL read(Sequence& aReadBytes, + sal_Int32 nBytesToRead) override; + virtual void SAL_CALL write(const Sequence& aData) override; + virtual void SAL_CALL flush() override; + virtual void SAL_CALL close() override; + virtual OUString SAL_CALL getDescription() override; + void setBridge(Reference); + int addClientURPToBuffer(const signed char* pBuffer, int nLen); + void* getContext(); + inline static int g_connectionCount = 0; + +private: + std::shared_ptr> m_pBuffer; + void* m_pRecieveFromLOContext; + int (*m_fnReceiveURPFromLO)(void* pContext, const signed char* pBuffer, int nLen); + Reference m_URPBridge; + std::atomic m_closed = false; + std::condition_variable m_URPInBuffer; + std::mutex m_bufferMutex; +}; + +FunctionBasedURPConnection::FunctionBasedURPConnection( + void* pRecieveFromLOContext, + int (*fnRecieveFromLO)(void* pContext, const signed char* pBuffer, int nLen)) + : m_pBuffer(std::make_shared>()) + , m_pRecieveFromLOContext(pRecieveFromLOContext) + , m_fnReceiveURPFromLO(fnRecieveFromLO) +{ + g_connectionCount++; +} + +FunctionBasedURPConnection::~FunctionBasedURPConnection() +{ + Reference xComp(m_URPBridge, UNO_QUERY_THROW); + xComp->dispose(); // TODO: check this doesn't deadlock +} + +int sendURPToLO(void* pContext /* FunctionBasedURPConnection* */, const signed char* pBuffer, + int nLen) +{ + return static_cast(pContext)->addClientURPToBuffer(pBuffer, nLen); +} + +int FunctionBasedURPConnection::addClientURPToBuffer(const signed char* pBuffer, int nLen) +{ + { + std::scoped_lock lock(m_bufferMutex); + + if (m_closed) + { + // We can't write URP to a closed connection + SAL_WARN("lok.urp", "A client attempted to write URP to a closed " + "FunctionBasedURPConnection... ignoring"); + return 0; + } + m_pBuffer->insert(m_pBuffer->end(), pBuffer, pBuffer + nLen); + } + m_URPInBuffer.notify_one(); + return nLen; +} + +void* FunctionBasedURPConnection::getContext() { return this; } + +sal_Int32 FunctionBasedURPConnection::read(Sequence& aReadBytes, sal_Int32 nBytesToRead) +{ + if (aReadBytes.getLength() != nBytesToRead) + { + aReadBytes.realloc(nBytesToRead); + } + + sal_Int8* result = aReadBytes.getArray(); + // As with osl::StreamPipe, we must always read nBytesToRead... + + { + std::unique_lock lock(m_bufferMutex); + + if (nBytesToRead < 0) + { + return 0; + } + m_URPInBuffer.wait( + lock, [this, nBytesToRead] { return static_cast(m_pBuffer->size()) >= nBytesToRead; }); + + std::copy(m_pBuffer->begin(), m_pBuffer->begin() + nBytesToRead, result); + m_pBuffer->erase(m_pBuffer->begin(), m_pBuffer->begin() + nBytesToRead); + } + + return nBytesToRead; +} + +void FunctionBasedURPConnection::write(const Sequence& aData) +{ + m_fnReceiveURPFromLO(m_pRecieveFromLOContext, aData.getConstArray(), aData.getLength()); +} + +void FunctionBasedURPConnection::flush() {} + +void FunctionBasedURPConnection::close() +{ + SAL_INFO("lok.urp", "Requested to close FunctionBasedURPConnection"); + m_closed = true; +} + +OUString FunctionBasedURPConnection::getDescription() { return ""; } + +void FunctionBasedURPConnection::setBridge(Reference xBridge) { m_URPBridge = xBridge; } +} + +static int +lo_startURP(LibreOfficeKit* /* pThis */, void* pRecieveFromLOContext, void** ppSendToLOContext, + int (*fnReceiveURPFromLO)(void* pContext, const signed char* pBuffer, int nLen), + int (**pfnSendURPToLO)(void* pContext, const signed char* pBuffer, int nLen)) +{ + // Here we will roughly do what desktop LO does when one passes a command-line switch like + // --accept=socket,port=nnnn;urp;StarOffice.ServiceManager. Except that no listening socket will + // be created. The communication to the URP will be through the fnReceiveURPFromLO and pfnSendURPToLO functions. + + rtl::Reference connection( + new FunctionBasedURPConnection(pRecieveFromLOContext, fnReceiveURPFromLO)); + + *pfnSendURPToLO = sendURPToLO; + *ppSendToLOContext = connection->getContext(); + + Reference xBridgeFactory = css::bridge::BridgeFactory::create(xContext); + + Reference xInstanceProvider(new FunctionBasedURPInstanceProvider(xContext)); + + Reference xBridge(xBridgeFactory->createBridge( + "functionurp" + OUString::number(FunctionBasedURPConnection::g_connectionCount), "urp", + connection, xInstanceProvider)); + + connection->setBridge(std::move(xBridge)); + + return true; +} + +/** + * Stop a function based URP connection that you started with lo_startURP above + * + * @param pSendToLOContext a pointer to the context you got back using your ppSendToLOContext before */ +static void lo_stopURP(LibreOfficeKit* /* pThis */, + void* pSendToLOContext /* FunctionBasedURPConnection* */) +{ + static_cast(pSendToLOContext)->close(); +} + static void lo_registerCallback (LibreOfficeKit* pThis, LibreOfficeKitCallback pCallback, void* pData) diff --git a/download.lst b/download.lst index 02adb7a75fd4..212077f775b6 100644 --- a/download.lst +++ b/download.lst @@ -249,6 +249,11 @@ ICU_DATA_TARBALL := icu4c-73_2-data.zip # three static lines # so that git cherry-pick # will not run into conflicts +JAVA_WEBSOCKET_SHA256SUM := a6828b35d1f938fee2335945f3d3c563cbbfa58ce7eb0bf72778d0fa7a550720 +JAVA_WEBSOCKET_TARBALL := Java-WebSocket-1.5.4.tar.gz +# three static lines +# so that git cherry-pick +# will not run into conflicts JFREEREPORT_FLOW_ENGINE_SHA256SUM := 233f66e8d25c5dd971716d4200203a612a407649686ef3b52075d04b4c9df0dd JFREEREPORT_FLOW_ENGINE_TARBALL := ba2930200c9f019c2d93a8c88c651a0f-flow-engine-0.9.4.zip JFREEREPORT_FLUTE_SHA256SUM := 1b5b24f7bc543c0362b667692f78db8bab4ed6dafc6172f104d0bd3757d8a133 diff --git a/external/Module_external.mk b/external/Module_external.mk index 9feb4e21addf..ad719e7c4c37 100644 --- a/external/Module_external.mk +++ b/external/Module_external.mk @@ -50,6 +50,7 @@ $(eval $(call gb_Module_add_moduledirs,external,\ $(call gb_Helper_optional,HUNSPELL,hunspell) \ $(call gb_Helper_optional,HYPHEN,hyphen) \ $(call gb_Helper_optional,ICU,icu) \ + $(call gb_Helper_optional,JAVA_WEBSOCKET,java_websocket) \ $(call gb_Helper_optional,JFREEREPORT,jfreereport) \ $(call gb_Helper_optional,LIBJPEG_TURBO,libjpeg-turbo) \ $(call gb_Helper_optional,LCMS2,lcms2) \ diff --git a/external/java_websocket/ExternalPackage_java_websocket.mk b/external/java_websocket/ExternalPackage_java_websocket.mk new file mode 100644 index 000000000000..2b3d1d87f3c0 --- /dev/null +++ b/external/java_websocket/ExternalPackage_java_websocket.mk @@ -0,0 +1,16 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_ExternalPackage_ExternalPackage,java_websocket_project,java_websocket)) + +$(eval $(call gb_ExternalPackage_use_external_project,java_websocket_project,java_websocket)) + +$(eval $(call gb_ExternalPackage_add_file,java_websocket_project,$(LIBO_SHARE_JAVA_FOLDER)/java_websocket.jar,dist/java_websocket.jar)) + +# vim: set noet sw=4 ts=4: diff --git a/external/java_websocket/ExternalProject_java_websocket.mk b/external/java_websocket/ExternalProject_java_websocket.mk new file mode 100644 index 000000000000..0d713aadc02d --- /dev/null +++ b/external/java_websocket/ExternalProject_java_websocket.mk @@ -0,0 +1,31 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_ExternalProject_ExternalProject,java_websocket)) + +$(eval $(call gb_ExternalProject_register_targets,java_websocket,\ + build \ +)) + +$(call gb_ExternalProject_get_state_target,java_websocket,build) : + $(call gb_Trace_StartRange,java_websocket,EXTERNAL) + $(call gb_ExternalProject_run,build,\ + JAVA_HOME=$(JAVA_HOME_FOR_BUILD) \ + $(ICECREAM_RUN) "$(ANT)" \ + $(if $(verbose),-v,-q) \ + -f build.xml \ + -Dbuild.label="build-$(LIBO_VERSION_MAJOR).$(LIBO_VERSION_MINOR).$(LIBO_VERSION_MICRO).$(LIBO_VERSION_PATCH)" \ + -Dant.build.javac.source=$(JAVA_SOURCE_VER) \ + -Dant.build.javac.target=$(JAVA_TARGET_VER) \ + $(if $(debug),-Dbuild.debug="on") \ + jar \ + ) + $(call gb_Trace_EndRange,java_websocket,EXTERNAL) + +# vim: set noet sw=4 ts=4: diff --git a/external/java_websocket/Makefile b/external/java_websocket/Makefile new file mode 100644 index 000000000000..e4968cf85fb6 --- /dev/null +++ b/external/java_websocket/Makefile @@ -0,0 +1,7 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- + +module_directory:=$(dir $(realpath $(firstword $(MAKEFILE_LIST)))) + +include $(module_directory)/../../solenv/gbuild/partial_build.mk + +# vim: set noet sw=4 ts=4: diff --git a/external/java_websocket/Module_java_websocket.mk b/external/java_websocket/Module_java_websocket.mk new file mode 100644 index 000000000000..3dcb12dde43e --- /dev/null +++ b/external/java_websocket/Module_java_websocket.mk @@ -0,0 +1,18 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Module_Module,java_websocket)) + +$(eval $(call gb_Module_add_targets,java_websocket,\ + ExternalPackage_java_websocket \ + ExternalProject_java_websocket \ + UnpackedTarball_java_websocket \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/external/java_websocket/README b/external/java_websocket/README new file mode 100644 index 000000000000..def0298a09cb --- /dev/null +++ b/external/java_websocket/README @@ -0,0 +1,3 @@ +Java WebSocket Implmentation from [https://github.com/TooTallNate/Java-WebSocket]. + +To send data over websockets, enabling uno over websockets. diff --git a/external/java_websocket/UnpackedTarball_java_websocket.mk b/external/java_websocket/UnpackedTarball_java_websocket.mk new file mode 100644 index 000000000000..cc8e7c259fc9 --- /dev/null +++ b/external/java_websocket/UnpackedTarball_java_websocket.mk @@ -0,0 +1,21 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_UnpackedTarball_UnpackedTarball,java_websocket)) + +$(eval $(call gb_UnpackedTarball_set_tarball,java_websocket,$(JAVA_WEBSOCKET_TARBALL),,java_websocket)) + +$(eval $(call gb_UnpackedTarball_set_patchlevel,java_websocket,1)) + +$(eval $(call gb_UnpackedTarball_add_patches,java_websocket,\ + external/java_websocket/patches/ant-build.patch \ + external/java_websocket/patches/no-slf4j.patch \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/external/java_websocket/patches/ant-build.patch b/external/java_websocket/patches/ant-build.patch new file mode 100644 index 000000000000..1757417d5122 --- /dev/null +++ b/external/java_websocket/patches/ant-build.patch @@ -0,0 +1,26 @@ +--- /dev/null 1970-01-01 01:00:00.000000000 +0100 ++++ b/build.xml 2023-08-30 11:43:05.152647141 +0100 +@@ -0,0 +1,23 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ diff --git a/external/java_websocket/patches/no-slf4j.patch b/external/java_websocket/patches/no-slf4j.patch new file mode 100644 index 000000000000..27296071eff7 --- /dev/null +++ b/external/java_websocket/patches/no-slf4j.patch @@ -0,0 +1,748 @@ +diff -ru a/src/main/java/org/java_websocket/AbstractWebSocket.java b/src/main/java/org/java_websocket/AbstractWebSocket.java +--- a/src/main/java/org/java_websocket/AbstractWebSocket.java 2023-07-20 21:24:05.000000000 +0100 ++++ b/src/main/java/org/java_websocket/AbstractWebSocket.java 2023-08-30 12:06:11.004719499 +0100 +@@ -33,9 +33,7 @@ + import java.util.concurrent.TimeUnit; + import org.java_websocket.framing.CloseFrame; + import org.java_websocket.util.NamedThreadFactory; +-import org.slf4j.Logger; +-import org.slf4j.LoggerFactory; +- ++import java.util.logging.Logger; + + /** + * Base class for additional implementations for the server as well as the client +@@ -47,7 +45,7 @@ + * + * @since 1.4.0 + */ +- private final Logger log = LoggerFactory.getLogger(AbstractWebSocket.class); ++ private final Logger log = Logger.getLogger(AbstractWebSocket.class.getName()); + + /** + * Attribute which allows you to deactivate the Nagle's algorithm +@@ -118,12 +116,12 @@ + synchronized (syncConnectionLost) { + this.connectionLostTimeout = TimeUnit.SECONDS.toNanos(connectionLostTimeout); + if (this.connectionLostTimeout <= 0) { +- log.trace("Connection lost timer stopped"); ++ log.fine("Connection lost timer stopped"); + cancelConnectionLostTimer(); + return; + } + if (this.websocketRunning) { +- log.trace("Connection lost timer restarted"); ++ log.fine("Connection lost timer restarted"); + //Reset all the pings + try { + ArrayList connections = new ArrayList<>(getConnections()); +@@ -135,7 +133,7 @@ + } + } + } catch (Exception e) { +- log.error("Exception during connection lost restart", e); ++ log.severe("Exception during connection lost restart" + " : " + e); + } + restartConnectionLostTimer(); + } +@@ -151,7 +149,7 @@ + synchronized (syncConnectionLost) { + if (connectionLostCheckerService != null || connectionLostCheckerFuture != null) { + this.websocketRunning = false; +- log.trace("Connection lost timer stopped"); ++ log.fine("Connection lost timer stopped"); + cancelConnectionLostTimer(); + } + } +@@ -165,10 +163,10 @@ + protected void startConnectionLostTimer() { + synchronized (syncConnectionLost) { + if (this.connectionLostTimeout <= 0) { +- log.trace("Connection lost timer deactivated"); ++ log.fine("Connection lost timer deactivated"); + return; + } +- log.trace("Connection lost timer started"); ++ log.fine("Connection lost timer started"); + this.websocketRunning = true; + restartConnectionLostTimer(); + } +@@ -228,14 +226,14 @@ + } + WebSocketImpl webSocketImpl = (WebSocketImpl) webSocket; + if (webSocketImpl.getLastPong() < minimumPongTime) { +- log.trace("Closing connection due to no pong received: {}", webSocketImpl); ++ log.fine("Closing connection due to no pong received: {}" + " : " + webSocketImpl); + webSocketImpl.closeConnection(CloseFrame.ABNORMAL_CLOSE, + "The connection was closed because the other endpoint did not respond with a pong in time. For more information check: https://github.com/TooTallNate/Java-WebSocket/wiki/Lost-connection-detection"); + } else { + if (webSocketImpl.isOpen()) { + webSocketImpl.sendPing(); + } else { +- log.trace("Trying to ping a non open connection: {}", webSocketImpl); ++ log.fine("Trying to ping a non open connection: {}" + " : " + webSocketImpl); + } + } + } +diff -ru a/src/main/java/org/java_websocket/drafts/Draft_6455.java b/src/main/java/org/java_websocket/drafts/Draft_6455.java +--- a/src/main/java/org/java_websocket/drafts/Draft_6455.java 2023-07-20 21:24:05.000000000 +0100 ++++ b/src/main/java/org/java_websocket/drafts/Draft_6455.java 2023-08-30 12:16:03.534083539 +0100 +@@ -66,8 +66,8 @@ + import org.java_websocket.protocols.Protocol; + import org.java_websocket.util.Base64; + import org.java_websocket.util.Charsetfunctions; +-import org.slf4j.Logger; +-import org.slf4j.LoggerFactory; ++import java.util.logging.Level; ++import java.util.logging.Logger; + + /** + * Implementation for the RFC 6455 websocket protocol This is the recommended class for your +@@ -110,7 +110,7 @@ + * + * @since 1.4.0 + */ +- private final Logger log = LoggerFactory.getLogger(Draft_6455.class); ++ private final Logger log = Logger.getLogger(Draft_6455.class.getName()); + + /** + * Attribute for the used extension in this draft +@@ -263,7 +263,7 @@ + throws InvalidHandshakeException { + int v = readVersion(handshakedata); + if (v != 13) { +- log.trace("acceptHandshakeAsServer - Wrong websocket version."); ++ log.fine("acceptHandshakeAsServer - Wrong websocket version."); + return HandshakeState.NOT_MATCHED; + } + HandshakeState extensionState = HandshakeState.NOT_MATCHED; +@@ -272,7 +272,7 @@ + if (knownExtension.acceptProvidedExtensionAsServer(requestedExtension)) { + negotiatedExtension = knownExtension; + extensionState = HandshakeState.MATCHED; +- log.trace("acceptHandshakeAsServer - Matching extension found: {}", negotiatedExtension); ++ log.fine("acceptHandshakeAsServer - Matching extension found: {}" + " : " + negotiatedExtension); + break; + } + } +@@ -281,7 +281,7 @@ + if (protocolState == HandshakeState.MATCHED && extensionState == HandshakeState.MATCHED) { + return HandshakeState.MATCHED; + } +- log.trace("acceptHandshakeAsServer - No matching extension or protocol found."); ++ log.fine("acceptHandshakeAsServer - No matching extension or protocol found."); + return HandshakeState.NOT_MATCHED; + } + +@@ -295,7 +295,7 @@ + for (IProtocol knownProtocol : knownProtocols) { + if (knownProtocol.acceptProvidedProtocol(requestedProtocol)) { + protocol = knownProtocol; +- log.trace("acceptHandshake - Matching protocol found: {}", protocol); ++ log.fine("acceptHandshake - Matching protocol found: {}" + " : " + protocol); + return HandshakeState.MATCHED; + } + } +@@ -306,12 +306,12 @@ + public HandshakeState acceptHandshakeAsClient(ClientHandshake request, ServerHandshake response) + throws InvalidHandshakeException { + if (!basicAccept(response)) { +- log.trace("acceptHandshakeAsClient - Missing/wrong upgrade or connection in handshake."); ++ log.fine("acceptHandshakeAsClient - Missing/wrong upgrade or connection in handshake."); + return HandshakeState.NOT_MATCHED; + } + if (!request.hasFieldValue(SEC_WEB_SOCKET_KEY) || !response + .hasFieldValue(SEC_WEB_SOCKET_ACCEPT)) { +- log.trace("acceptHandshakeAsClient - Missing Sec-WebSocket-Key or Sec-WebSocket-Accept"); ++ log.fine("acceptHandshakeAsClient - Missing Sec-WebSocket-Key or Sec-WebSocket-Accept"); + return HandshakeState.NOT_MATCHED; + } + +@@ -320,7 +320,7 @@ + seckeyChallenge = generateFinalKey(seckeyChallenge); + + if (!seckeyChallenge.equals(seckeyAnswer)) { +- log.trace("acceptHandshakeAsClient - Wrong key for Sec-WebSocket-Key."); ++ log.fine("acceptHandshakeAsClient - Wrong key for Sec-WebSocket-Key."); + return HandshakeState.NOT_MATCHED; + } + HandshakeState extensionState = HandshakeState.NOT_MATCHED; +@@ -329,7 +329,7 @@ + if (knownExtension.acceptProvidedExtensionAsClient(requestedExtension)) { + negotiatedExtension = knownExtension; + extensionState = HandshakeState.MATCHED; +- log.trace("acceptHandshakeAsClient - Matching extension found: {}", negotiatedExtension); ++ log.fine("acceptHandshakeAsClient - Matching extension found: {}" + " : " + negotiatedExtension); + break; + } + } +@@ -338,7 +338,7 @@ + if (protocolState == HandshakeState.MATCHED && extensionState == HandshakeState.MATCHED) { + return HandshakeState.MATCHED; + } +- log.trace("acceptHandshakeAsClient - No matching extension or protocol found."); ++ log.fine("acceptHandshakeAsClient - No matching extension or protocol found."); + return HandshakeState.NOT_MATCHED; + } + +@@ -467,8 +467,8 @@ + @Override + public ByteBuffer createBinaryFrame(Framedata framedata) { + getExtension().encodeFrame(framedata); +- if (log.isTraceEnabled()) { +- log.trace("afterEnconding({}): {}", framedata.getPayloadData().remaining(), ++ if (log.isLoggable(Level.FINE)) { ++ log.fine("afterEnconding({}): {}" + " : " + framedata.getPayloadData().remaining() + " : " + + (framedata.getPayloadData().remaining() > 1000 ? "too big to display" + : new String(framedata.getPayloadData().array()))); + } +@@ -587,8 +587,8 @@ + } + currentDecodingExtension.isFrameValid(frame); + currentDecodingExtension.decodeFrame(frame); +- if (log.isTraceEnabled()) { +- log.trace("afterDecoding({}): {}", frame.getPayloadData().remaining(), ++ if (log.isLoggable(Level.FINE)) { ++ log.fine("afterDecoding({}): {}" + " : " + frame.getPayloadData().remaining() + " : " + + (frame.getPayloadData().remaining() > 1000 ? "too big to display" + : new String(frame.getPayloadData().array()))); + } +@@ -615,7 +615,7 @@ + int payloadlength = oldPayloadlength; + int realpacketsize = oldRealpacketsize; + if (optcode == Opcode.PING || optcode == Opcode.PONG || optcode == Opcode.CLOSING) { +- log.trace("Invalid frame: more than 125 octets"); ++ log.fine("Invalid frame: more than 125 octets"); + throw new InvalidFrameException("more than 125 octets"); + } + if (payloadlength == 126) { +@@ -647,15 +647,15 @@ + */ + private void translateSingleFrameCheckLengthLimit(long length) throws LimitExceededException { + if (length > Integer.MAX_VALUE) { +- log.trace("Limit exedeed: Payloadsize is to big..."); ++ log.fine("Limit exedeed: Payloadsize is to big..."); + throw new LimitExceededException("Payloadsize is to big..."); + } + if (length > maxFrameSize) { +- log.trace("Payload limit reached. Allowed: {} Current: {}", maxFrameSize, length); ++ log.fine("Payload limit reached. Allowed: {} Current: {}" + " : " + maxFrameSize + " : " + length); + throw new LimitExceededException("Payload limit reached.", maxFrameSize); + } + if (length < 0) { +- log.trace("Limit underflow: Payloadsize is to little..."); ++ log.fine("Limit underflow: Payloadsize is to little..."); + throw new LimitExceededException("Payloadsize is to little..."); + } + } +@@ -670,7 +670,7 @@ + private void translateSingleFrameCheckPacketSize(int maxpacketsize, int realpacketsize) + throws IncompleteException { + if (maxpacketsize < realpacketsize) { +- log.trace("Incomplete frame: maxpacketsize < realpacketsize"); ++ log.fine("Incomplete frame: maxpacketsize < realpacketsize"); + throw new IncompleteException(realpacketsize); + } + } +@@ -903,7 +903,7 @@ + } else if (!frame.isFin() || curop == Opcode.CONTINUOUS) { + processFrameContinuousAndNonFin(webSocketImpl, frame, curop); + } else if (currentContinuousFrame != null) { +- log.error("Protocol error: Continuous frame sequence not completed."); ++ log.severe("Protocol error: Continuous frame sequence not completed."); + throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR, + "Continuous frame sequence not completed."); + } else if (curop == Opcode.TEXT) { +@@ -911,7 +911,7 @@ + } else if (curop == Opcode.BINARY) { + processFrameBinary(webSocketImpl, frame); + } else { +- log.error("non control or continious frame expected"); ++ log.severe("non control or continious frame expected"); + throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR, + "non control or continious frame expected"); + } +@@ -932,13 +932,13 @@ + } else if (frame.isFin()) { + processFrameIsFin(webSocketImpl, frame); + } else if (currentContinuousFrame == null) { +- log.error("Protocol error: Continuous frame sequence was not started."); ++ log.severe("Protocol error: Continuous frame sequence was not started."); + throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR, + "Continuous frame sequence was not started."); + } + //Check if the whole payload is valid utf8, when the opcode indicates a text + if (curop == Opcode.TEXT && !Charsetfunctions.isValidUTF8(frame.getPayloadData())) { +- log.error("Protocol error: Payload is not UTF8"); ++ log.severe("Protocol error: Payload is not UTF8"); + throw new InvalidDataException(CloseFrame.NO_UTF8); + } + //Checking if the current continuous frame contains a correct payload with the other frames combined +@@ -969,7 +969,7 @@ + * @param e the runtime exception + */ + private void logRuntimeException(WebSocketImpl webSocketImpl, RuntimeException e) { +- log.error("Runtime exception during onWebsocketMessage", e); ++ log.severe("Runtime exception during onWebsocketMessage" + " : " + e); + webSocketImpl.getWebSocketListener().onWebsocketError(webSocketImpl, e); + } + +@@ -999,7 +999,7 @@ + private void processFrameIsFin(WebSocketImpl webSocketImpl, Framedata frame) + throws InvalidDataException { + if (currentContinuousFrame == null) { +- log.trace("Protocol error: Previous continuous frame sequence not completed."); ++ log.fine("Protocol error: Previous continuous frame sequence not completed."); + throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR, + "Continuous frame sequence was not started."); + } +@@ -1036,7 +1036,7 @@ + */ + private void processFrameIsNotFin(Framedata frame) throws InvalidDataException { + if (currentContinuousFrame != null) { +- log.trace("Protocol error: Previous continuous frame sequence not completed."); ++ log.fine("Protocol error: Previous continuous frame sequence not completed."); + throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR, + "Previous continuous frame sequence not completed."); + } +@@ -1102,7 +1102,7 @@ + long totalSize = getByteBufferListSize(); + if (totalSize > maxFrameSize) { + clearBufferList(); +- log.trace("Payload limit reached. Allowed: {} Current: {}", maxFrameSize, totalSize); ++ log.fine("Payload limit reached. Allowed: {} Current: {}" + " : " + maxFrameSize + " : " + totalSize); + throw new LimitExceededException(maxFrameSize); + } + } +diff -ru a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java +--- a/src/main/java/org/java_websocket/server/WebSocketServer.java 2023-07-20 21:24:05.000000000 +0100 ++++ b/src/main/java/org/java_websocket/server/WebSocketServer.java 2023-08-30 12:06:46.372798355 +0100 +@@ -67,8 +67,7 @@ + import org.java_websocket.framing.Framedata; + import org.java_websocket.handshake.ClientHandshake; + import org.java_websocket.handshake.Handshakedata; +-import org.slf4j.Logger; +-import org.slf4j.LoggerFactory; ++import java.util.logging.Logger; + + /** + * WebSocketServer is an abstract class that only takes care of the +@@ -84,7 +83,7 @@ + * + * @since 1.4.0 + */ +- private final Logger log = LoggerFactory.getLogger(WebSocketServer.class); ++ private final Logger log = Logger.getLogger(WebSocketServer.class.getName()); + + /** + * Holds the list of active WebSocket connections. "Active" means WebSocket handshake is complete +@@ -611,7 +610,7 @@ + try { + selector.close(); + } catch (IOException e) { +- log.error("IOException during selector.close", e); ++ log.severe("IOException during selector.close" + " : " + e); + onError(null, e); + } + } +@@ -619,7 +618,7 @@ + try { + server.close(); + } catch (IOException e) { +- log.error("IOException during server.close", e); ++ log.severe("IOException during server.close" + " : " + e); + onError(null, e); + } + } +@@ -677,13 +676,13 @@ + } catch (IOException e) { + // there is nothing that must be done here + } +- log.trace("Connection closed because of exception", ex); ++ log.fine("Connection closed because of exception" + " : " + ex); + } + } + } + + private void handleFatal(WebSocket conn, Exception e) { +- log.error("Shutdown due to fatal error", e); ++ log.severe("Shutdown due to fatal error" + " : " + e); + onError(conn, e); + + String causeMessage = e.getCause() != null ? " caused by " + e.getCause().getClass().getName() : ""; +@@ -692,7 +691,7 @@ + stop(0, errorMessage); + } catch (InterruptedException e1) { + Thread.currentThread().interrupt(); +- log.error("Interrupt during stop", e); ++ log.severe("Interrupt during stop" + " : " + e); + onError(null, e1); + } + +@@ -760,8 +759,8 @@ + removed = this.connections.remove(ws); + } else { + //Don't throw an assert error if the ws is not in the list. e.g. when the other endpoint did not send any handshake. see #512 +- log.trace( +- "Removing connection which is not in the connections collection! Possible no handshake received! {}", ++ log.fine( ++ "Removing connection which is not in the connections collection! Possible no handshake received! {}" + " : " + + ws); + } + } +@@ -1065,7 +1064,7 @@ + setUncaughtExceptionHandler(new UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { +- log.error("Uncaught exception in thread {}: {}", t.getName(), e); ++ log.severe("Uncaught exception in thread {}: {}" + " : " + t.getName() + " : " + e); + } + }); + } +@@ -1089,11 +1088,11 @@ + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (VirtualMachineError | ThreadDeath | LinkageError e) { +- log.error("Got fatal error in worker thread {}", getName()); ++ log.severe("Got fatal error in worker thread {}" + " : " + getName()); + Exception exception = new Exception(e); + handleFatal(ws, exception); + } catch (Throwable e) { +- log.error("Uncaught exception in thread {}: {}", getName(), e); ++ log.severe("Uncaught exception in thread {}: {}" + " : " + getName() + " : " + e); + if (ws != null) { + Exception exception = new Exception(e); + onWebsocketError(ws, exception); +@@ -1113,7 +1112,7 @@ + try { + ws.decode(buf); + } catch (Exception e) { +- log.error("Error while reading from remote connection", e); ++ log.severe("Error while reading from remote connection" + " : " + e); + } finally { + pushBuffer(buf); + } +diff -ru a/src/main/java/org/java_websocket/SSLSocketChannel2.java b/src/main/java/org/java_websocket/SSLSocketChannel2.java +--- a/src/main/java/org/java_websocket/SSLSocketChannel2.java 2023-07-20 21:24:05.000000000 +0100 ++++ b/src/main/java/org/java_websocket/SSLSocketChannel2.java 2023-08-30 12:05:33.937636854 +0100 +@@ -47,8 +47,8 @@ + import javax.net.ssl.SSLException; + import javax.net.ssl.SSLSession; + import org.java_websocket.interfaces.ISSLChannel; +-import org.slf4j.Logger; +-import org.slf4j.LoggerFactory; ++import java.util.logging.Level; ++import java.util.logging.Logger; + + /** + * Implements the relevant portions of the SocketChannel interface with the SSLEngine wrapper. +@@ -66,7 +66,7 @@ + * + * @since 1.4.0 + */ +- private final Logger log = LoggerFactory.getLogger(SSLSocketChannel2.class); ++ private final Logger log = Logger.getLogger(SSLSocketChannel2.class.getName()); + + protected ExecutorService exec; + +@@ -256,13 +256,13 @@ + inCrypt = ByteBuffer.allocate(netBufferMax); + } + } +- if (inData.remaining() != 0 && log.isTraceEnabled()) { +- log.trace(new String(inData.array(), inData.position(), inData.remaining())); ++ if (inData.remaining() != 0 && log.isLoggable(Level.FINE)) { ++ log.fine(new String(inData.array(), inData.position(), inData.remaining())); + } + inData.rewind(); + inData.flip(); +- if (inCrypt.remaining() != 0 && log.isTraceEnabled()) { +- log.trace(new String(inCrypt.array(), inCrypt.position(), inCrypt.remaining())); ++ if (inCrypt.remaining() != 0 && log.isLoggable(Level.FINE)) { ++ log.fine(new String(inCrypt.array(), inCrypt.position(), inCrypt.remaining())); + } + inCrypt.rewind(); + inCrypt.flip(); +@@ -498,4 +498,4 @@ + saveCryptData = null; + } + } +-} +\ No newline at end of file ++} +diff -ru a/src/main/java/org/java_websocket/SSLSocketChannel.java b/src/main/java/org/java_websocket/SSLSocketChannel.java +--- a/src/main/java/org/java_websocket/SSLSocketChannel.java 2023-07-20 21:24:05.000000000 +0100 ++++ b/src/main/java/org/java_websocket/SSLSocketChannel.java 2023-08-30 11:55:09.427244528 +0100 +@@ -39,8 +39,7 @@ + import javax.net.ssl.SSLSession; + import org.java_websocket.interfaces.ISSLChannel; + import org.java_websocket.util.ByteBufferUtils; +-import org.slf4j.Logger; +-import org.slf4j.LoggerFactory; ++import java.util.logging.Logger; + + + /** +@@ -70,7 +69,7 @@ + * + * @since 1.4.0 + */ +- private final Logger log = LoggerFactory.getLogger(SSLSocketChannel.class); ++ private final Logger log = Logger.getLogger(SSLSocketChannel.class.getName()); + + /** + * The underlying socket channel +@@ -148,7 +147,7 @@ + try { + socketChannel.close(); + } catch (IOException e) { +- log.error("Exception during the closing of the channel", e); ++ log.severe("Exception during the closing of the channel" + " : " + e); + } + } + } +@@ -176,7 +175,7 @@ + try { + result = engine.unwrap(peerNetData, peerAppData); + } catch (SSLException e) { +- log.error("SSLException during unwrap", e); ++ log.severe("SSLException during unwrap" + " : " + e); + throw e; + } + switch (result.getStatus()) { +@@ -490,7 +489,7 @@ + try { + engine.closeInbound(); + } catch (Exception e) { +- log.error( ++ log.severe( + "This engine was forced to close inbound, without having received the proper SSL/TLS close notification message from the peer, due to end of stream."); + } + closeConnection(); +@@ -536,4 +535,4 @@ + public SSLEngine getSSLEngine() { + return engine; + } +-} +\ No newline at end of file ++} +diff -ru a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java +--- a/src/main/java/org/java_websocket/WebSocketImpl.java 2023-07-20 21:24:05.000000000 +0100 ++++ b/src/main/java/org/java_websocket/WebSocketImpl.java 2023-08-30 12:12:26.045577651 +0100 +@@ -61,8 +61,8 @@ + import org.java_websocket.protocols.IProtocol; + import org.java_websocket.server.WebSocketServer.WebSocketWorker; + import org.java_websocket.util.Charsetfunctions; +-import org.slf4j.Logger; +-import org.slf4j.LoggerFactory; ++import java.util.logging.Level; ++import java.util.logging.Logger; + + /** + * Represents one end (client or server) of a single WebSocketImpl connection. Takes care of the +@@ -95,7 +95,7 @@ + * + * @since 1.4.0 + */ +- private final Logger log = LoggerFactory.getLogger(WebSocketImpl.class); ++ private final Logger log = Logger.getLogger(WebSocketImpl.class.getName()); + + /** + * Queue of buffers that need to be sent to the client. +@@ -224,8 +224,8 @@ + */ + public void decode(ByteBuffer socketBuffer) { + assert (socketBuffer.hasRemaining()); +- if (log.isTraceEnabled()) { +- log.trace("process({}): ({})", socketBuffer.remaining(), ++ if (log.isLoggable(Level.FINE)) { ++ log.fine("process({}): ({})" + " : " + socketBuffer.remaining() + " : " + + (socketBuffer.remaining() > 1000 ? "too big to display" + : new String(socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining()))); + } +@@ -280,7 +280,7 @@ + socketBuffer.reset(); + Handshakedata tmphandshake = d.translateHandshake(socketBuffer); + if (!(tmphandshake instanceof ClientHandshake)) { +- log.trace("Closing due to wrong handshake"); ++ log.fine("Closing due to wrong handshake"); + closeConnectionDueToWrongHandshake( + new InvalidDataException(CloseFrame.PROTOCOL_ERROR, "wrong http function")); + return false; +@@ -293,11 +293,11 @@ + try { + response = wsl.onWebsocketHandshakeReceivedAsServer(this, d, handshake); + } catch (InvalidDataException e) { +- log.trace("Closing due to wrong handshake. Possible handshake rejection", e); ++ log.fine("Closing due to wrong handshake. Possible handshake rejection" + " : " + e); + closeConnectionDueToWrongHandshake(e); + return false; + } catch (RuntimeException e) { +- log.error("Closing due to internal server error", e); ++ log.severe("Closing due to internal server error" + " : " + e); + wsl.onWebsocketError(this, e); + closeConnectionDueToInternalServerError(e); + return false; +@@ -313,7 +313,7 @@ + } + } + if (draft == null) { +- log.trace("Closing due to protocol error: no draft matches"); ++ log.fine("Closing due to protocol error: no draft matches"); + closeConnectionDueToWrongHandshake( + new InvalidDataException(CloseFrame.PROTOCOL_ERROR, "no draft matches")); + } +@@ -322,7 +322,7 @@ + // special case for multiple step handshakes + Handshakedata tmphandshake = draft.translateHandshake(socketBuffer); + if (!(tmphandshake instanceof ClientHandshake)) { +- log.trace("Closing due to protocol error: wrong http function"); ++ log.fine("Closing due to protocol error: wrong http function"); + flushAndClose(CloseFrame.PROTOCOL_ERROR, "wrong http function", false); + return false; + } +@@ -333,7 +333,7 @@ + open(handshake); + return true; + } else { +- log.trace("Closing due to protocol error: the handshake did finally not match"); ++ log.fine("Closing due to protocol error: the handshake did finally not match"); + close(CloseFrame.PROTOCOL_ERROR, "the handshake did finally not match"); + } + return false; +@@ -342,7 +342,7 @@ + draft.setParseMode(role); + Handshakedata tmphandshake = draft.translateHandshake(socketBuffer); + if (!(tmphandshake instanceof ServerHandshake)) { +- log.trace("Closing due to protocol error: wrong http function"); ++ log.fine("Closing due to protocol error: wrong http function"); + flushAndClose(CloseFrame.PROTOCOL_ERROR, "wrong http function", false); + return false; + } +@@ -352,11 +352,11 @@ + try { + wsl.onWebsocketHandshakeReceivedAsClient(this, handshakerequest, handshake); + } catch (InvalidDataException e) { +- log.trace("Closing due to invalid data exception. Possible handshake rejection", e); ++ log.fine("Closing due to invalid data exception. Possible handshake rejection" + " : " + e); + flushAndClose(e.getCloseCode(), e.getMessage(), false); + return false; + } catch (RuntimeException e) { +- log.error("Closing since client was never connected", e); ++ log.severe("Closing since client was never connected" + " : " + e); + wsl.onWebsocketError(this, e); + flushAndClose(CloseFrame.NEVER_CONNECTED, e.getMessage(), false); + return false; +@@ -364,12 +364,12 @@ + open(handshake); + return true; + } else { +- log.trace("Closing due to protocol error: draft {} refuses handshake", draft); ++ log.fine("Closing due to protocol error: draft {} refuses handshake" + " : " + draft); + close(CloseFrame.PROTOCOL_ERROR, "draft " + draft + " refuses handshake"); + } + } + } catch (InvalidHandshakeException e) { +- log.trace("Closing due to invalid handshake", e); ++ log.fine("Closing due to invalid handshake" + " : " + e); + close(e); + } + } catch (IncompleteHandshakeException e) { +@@ -398,24 +398,24 @@ + try { + frames = draft.translateFrame(socketBuffer); + for (Framedata f : frames) { +- log.trace("matched frame: {}", f); ++ log.fine("matched frame: {}" + " : " + f); + draft.processFrame(this, f); + } + } catch (LimitExceededException e) { + if (e.getLimit() == Integer.MAX_VALUE) { +- log.error("Closing due to invalid size of frame", e); ++ log.severe("Closing due to invalid size of frame" + " : " + e); + wsl.onWebsocketError(this, e); + } + close(e); + } catch (InvalidDataException e) { +- log.error("Closing due to invalid data in frame", e); ++ log.severe("Closing due to invalid data in frame" + " : " + e); + wsl.onWebsocketError(this, e); + close(e); + } catch (VirtualMachineError | ThreadDeath | LinkageError e) { +- log.error("Got fatal error during frame processing"); ++ log.severe("Got fatal error during frame processing"); + throw e; + } catch (Error e) { +- log.error("Closing web socket due to an error during frame processing"); ++ log.severe("Closing web socket due to an error during frame processing"); + Exception exception = new Exception(e); + wsl.onWebsocketError(this, exception); + String errorMessage = "Got error " + e.getClass().getName(); +@@ -491,7 +491,7 @@ + sendFrame(closeFrame); + } + } catch (InvalidDataException e) { +- log.error("generated frame is invalid", e); ++ log.severe("generated frame is invalid" + " : " + e); + wsl.onWebsocketError(this, e); + flushAndClose(CloseFrame.ABNORMAL_CLOSE, "generated frame is invalid", false); + } +@@ -551,9 +551,9 @@ + channel.close(); + } catch (IOException e) { + if (e.getMessage() != null && e.getMessage().equals("Broken pipe")) { +- log.trace("Caught IOException: Broken pipe during closeConnection()", e); ++ log.fine("Caught IOException: Broken pipe during closeConnection()" + " : " + e); + } else { +- log.error("Exception during channel.close()", e); ++ log.severe("Exception during channel.close()" + " : " + e); + wsl.onWebsocketError(this, e); + } + } +@@ -601,7 +601,7 @@ + try { + wsl.onWebsocketClosing(this, code, message, remote); + } catch (RuntimeException e) { +- log.error("Exception in onWebsocketClosing", e); ++ log.severe("Exception in onWebsocketClosing" + " : " + e); + wsl.onWebsocketError(this, e); + } + if (draft != null) { +@@ -678,7 +678,7 @@ + } + ArrayList outgoingFrames = new ArrayList<>(); + for (Framedata f : frames) { +- log.trace("send frame: {}", f); ++ log.fine("send frame: {}" + " : " + f); + outgoingFrames.add(draft.createBinaryFrame(f)); + } + write(outgoingFrames); +@@ -729,7 +729,7 @@ + // Stop if the client code throws an exception + throw new InvalidHandshakeException("Handshake data rejected by client."); + } catch (RuntimeException e) { +- log.error("Exception in startHandshake", e); ++ log.severe("Exception in startHandshake" + " : " + e); + wsl.onWebsocketError(this, e); + throw new InvalidHandshakeException("rejected because of " + e); + } +@@ -739,8 +739,8 @@ + } + + private void write(ByteBuffer buf) { +- log.trace("write({}): {}", buf.remaining(), +- buf.remaining() > 1000 ? "too big to display" : new String(buf.array())); ++ log.fine("write({}): {}" + " : " + buf.remaining() + " : " + ++ (buf.remaining() > 1000 ? "too big to display" : new String(buf.array()))); + + outQueue.add(buf); + wsl.onWriteDemand(this); +@@ -760,7 +760,7 @@ + } + + private void open(Handshakedata d) { +- log.trace("open using draft: {}", draft); ++ log.fine("open using draft: {}" + " : " + draft); + readyState = ReadyState.OPEN; + updateLastPong(); + try { diff --git a/include/LibreOfficeKit/LibreOfficeKit.h b/include/LibreOfficeKit/LibreOfficeKit.h index ba51c5afeea9..5e77e30549d0 100644 --- a/include/LibreOfficeKit/LibreOfficeKit.h +++ b/include/LibreOfficeKit/LibreOfficeKit.h @@ -131,6 +131,15 @@ struct _LibreOfficeKitClass /// @see lok::Office::trimMemory /// @since LibreOffice 7.6 void (*trimMemory) (LibreOfficeKit* pThis, int nTarget); + + /// @see lok::Office::startURP + int (*startURP)(LibreOfficeKit* pThis, void* pReceiveURPFromLOContext, + void** ppSendURPToLOContext, + int (*fnReceiveURPFromLO)(void* pContext, const signed char* pBuffer, int nLen), + int (**pfnSendURPToLO)(void* pContext, const signed char* pBuffer, int nLen)); + + /// @see lok::Office::stopURP + void (*stopURP)(LibreOfficeKit* pThis, void* pSendURPToLOContext); }; #define LIBREOFFICEKIT_DOCUMENT_HAS(pDoc,member) LIBREOFFICEKIT_HAS_MEMBER(LibreOfficeKitDocumentClass,member,(pDoc)->pClass->nSize) diff --git a/include/LibreOfficeKit/LibreOfficeKit.hxx b/include/LibreOfficeKit/LibreOfficeKit.hxx index 65f263c368a6..ff3974417c69 100644 --- a/include/LibreOfficeKit/LibreOfficeKit.hxx +++ b/include/LibreOfficeKit/LibreOfficeKit.hxx @@ -1166,6 +1166,32 @@ public: { mpThis->pClass->trimMemory(mpThis, nTarget); } + + /** + * Start a UNO acceptor using the function pointers provides to read and write data to/from the acceptor. + * + * @param pReceiveURPFromLOContext A pointer that will be passed to your fnRecieveURPFromLO function + * @param ppSendURPToLOContext A pointer to a pointer that you should give to the function passing URP to LO will be stored in this. + * @param fnReceiveURPFromLO A function pointer that LO should use to pass URP back to the caller + * @param pfnSendURPToLO A function pointer pointer that the caller should use to pass URP to LO will be stored in this. + */ + bool startURP(void* pReceiveURPFromLOContext, void** ppSendURPToLOContext, + int (*fnReceiveURPFromLO)(void* pContext, const signed char* pBuffer, int nLen), + int (**pfnSendURPToLO)(void* pContext, const signed char* pBuffer, int nLen)) + { + return mpThis->pClass->startURP(mpThis, pReceiveURPFromLOContext, ppSendURPToLOContext, + fnReceiveURPFromLO, pfnSendURPToLO); + } + + /** + * Stop a function based URP connection you previously started with startURP + * + * @param pSendURPToLOContext the context you got back in the ppSendURPToLOContext argument when starting the connection + */ + void stopURP(void* pSendURPToLOContext) + { + mpThis->pClass->stopURP(mpThis, pSendURPToLOContext); + } }; /// Factory method to create a lok::Office instance. diff --git a/include/sal/log-areas.dox b/include/sal/log-areas.dox index 646c7b3d58a2..5540c0d2f228 100644 --- a/include/sal/log-areas.dox +++ b/include/sal/log-areas.dox @@ -328,6 +328,7 @@ certain functionality. @li @c lok.tiledrendering @li @c lok.dialog @li @c lok.a11y - LOK accessibility +@li @c lok.urp - Uno Remote Protocol @section l10ntools diff --git a/readlicense_oo/license/license.xml b/readlicense_oo/license/license.xml index 637f85d7239c..57d270e15b94 100644 --- a/readlicense_oo/license/license.xml +++ b/readlicense_oo/license/license.xml @@ -253,6 +253,24 @@ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+
+

Java WebSocket

+

The following software may be included in this product: Java WebSocket. Use of any of this software is governed + by the terms of the license below:

+

The MIT License

+

Copyright (c) 2010-2020 Nathan Rajlich

+

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + documentation files (the "Software"), to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

+

The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software.

+

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT + SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE.

+

Flute

The following software may be included in this product: Flute. Use of any of this software is governed by the diff --git a/ridljar/Jar_libreoffice.mk b/ridljar/Jar_libreoffice.mk index d34ae3f5ebda..76a56eedc078 100644 --- a/ridljar/Jar_libreoffice.mk +++ b/ridljar/Jar_libreoffice.mk @@ -18,11 +18,16 @@ $(eval $(call gb_Jar_use_jars,libreoffice, \ unoloader \ )) +$(eval $(call gb_Jar_use_externals,libreoffice,\ + java_websocket \ +)) + $(eval $(call gb_Jar_set_packageroot,libreoffice,com)) $(eval $(call gb_Jar_set_manifest,libreoffice,$(SRCDIR)/ridljar/util/manifest)) $(eval $(call gb_Jar_add_manifest_classpath,libreoffice, \ + java_websocket.jar \ unoloader.jar \ $(if $(filter MACOSX,$(OS)),../../Frameworks/,../) \ )) @@ -63,6 +68,9 @@ $(eval $(call gb_Jar_add_sourcefiles,libreoffice,\ ridljar/com/sun/star/lib/connections/socket/SocketConnection \ ridljar/com/sun/star/lib/connections/socket/socketAcceptor \ ridljar/com/sun/star/lib/connections/socket/socketConnector \ + ridljar/com/sun/star/lib/connections/websocket/ConnectionDescriptor \ + ridljar/com/sun/star/lib/connections/websocket/WebsocketConnection \ + ridljar/com/sun/star/lib/connections/websocket/websocketConnector \ ridljar/com/sun/star/lib/uno/Proxy \ ridljar/com/sun/star/lib/uno/adapter/ByteArrayToXInputStreamAdapter \ ridljar/com/sun/star/lib/uno/adapter/InputStreamToXInputStreamAdapter \ diff --git a/ridljar/com/sun/star/comp/helper/Bootstrap.java b/ridljar/com/sun/star/comp/helper/Bootstrap.java index edf21d7d2adf..6b371f50324b 100644 --- a/ridljar/com/sun/star/comp/helper/Bootstrap.java +++ b/ridljar/com/sun/star/comp/helper/Bootstrap.java @@ -32,6 +32,7 @@ import com.sun.star.lib.util.NativeLibraryLoader; import com.sun.star.loader.XImplementationLoader; import com.sun.star.uno.UnoRuntime; import com.sun.star.uno.XComponentContext; +import com.sun.star.beans.XPropertySet; import java.io.BufferedReader; import java.io.File; @@ -397,6 +398,68 @@ public class Bootstrap { return xContext; } + /** + * Bootstraps the component context from a websocket location. + * + * @param url + * the ws:// or wss:// url of the websocket server + * + * @throws BootstrapException if things go awry. + * + * @return a bootstrapped component context. + * + * @since LibreOffice 24.2 + */ + public static final XComponentContext bootstrapWebsocketConnection( String url ) + throws BootstrapException { + + XComponentContext xContext = null; + + try { + // create default local component context + XComponentContext xLocalContext = + createInitialComponentContext( (Map) null ); + if ( xLocalContext == null ) + throw new BootstrapException( "no local component context!" ); + + // initial service manager + XMultiComponentFactory xLocalServiceManager = + xLocalContext.getServiceManager(); + if ( xLocalServiceManager == null ) + throw new BootstrapException( "no initial service manager!" ); + + // create a URL resolver + XUnoUrlResolver xUrlResolver = + UnoUrlResolver.create( xLocalContext ); + + // connection string + String sConnect = "uno:websocket" + + ",url=" + url + + ";urp;StarOffice.ComponentContext"; + + try { + // try to connect to office + Object xOfficeServiceManager = xUrlResolver.resolve(sConnect); + + xContext = UnoRuntime.queryInterface(XComponentContext.class, xOfficeServiceManager); + + if ( xContext == null ) + throw new BootstrapException( "no component context!" ); + } catch ( com.sun.star.connection.NoConnectException ex ) { + throw new BootstrapException(ex); + } + } catch ( BootstrapException e ) { + throw e; + } catch ( java.lang.RuntimeException e ) { + throw e; + } catch ( java.lang.Exception e ) { + throw new BootstrapException( e ); + } + + return xContext; + } + + private static final Random randomPipeName = new Random(); private static void pipe( diff --git a/ridljar/com/sun/star/lib/connections/websocket/ConnectionDescriptor.java b/ridljar/com/sun/star/lib/connections/websocket/ConnectionDescriptor.java new file mode 100644 index 000000000000..439a52551726 --- /dev/null +++ b/ridljar/com/sun/star/lib/connections/websocket/ConnectionDescriptor.java @@ -0,0 +1,60 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package com.sun.star.lib.connections.websocket; + +import java.util.Arrays; +import java.util.Iterator; + +/** + * Helper class for websocketConnector. + */ +final class ConnectionDescriptor { + public ConnectionDescriptor(String description) + throws com.sun.star.lang.IllegalArgumentException { + Iterator descriptionParts = Arrays.stream(description.split(",")).iterator(); + descriptionParts + .next(); // skip over the first part as it's the protocol not a real parameter + while (descriptionParts.hasNext()) + { + String parameter = descriptionParts.next(); + String[] pair = parameter.split("=", 2); + + if (pair.length != 2) + { + throw new com.sun.star.lang.IllegalArgumentException( + String.format("parameter %s lacks '='", parameter)); + } + + String key = pair[0]; + String value = pair[1]; + if (key.equalsIgnoreCase("url")) { + url = value; + } + } + } + + public String getURL() { + return url; + } + + private String url = null; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/connections/websocket/WebsocketConnection.java b/ridljar/com/sun/star/lib/connections/websocket/WebsocketConnection.java new file mode 100644 index 000000000000..7f522df409d1 --- /dev/null +++ b/ridljar/com/sun/star/lib/connections/websocket/WebsocketConnection.java @@ -0,0 +1,335 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package com.sun.star.lib.connections.websocket; + + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.net.ProtocolException; +import java.net.Socket; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; + +import javax.swing.text.html.HTMLDocument.Iterator; + +import com.sun.star.connection.XConnection; +import com.sun.star.connection.XConnectionBroadcaster; +import com.sun.star.io.XStreamListener; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; + +/** + * The WebsocketConnection implements the XConnection interface + * and is uses by the WebsocketConnector. + * + *

This class is not part of the provided api.

+ * + * @see com.sun.star.lib.connections.socket.socketAcceptor + * @see com.sun.star.lib.connections.socket.socketConnector + * @see com.sun.star.connection.XConnection + */ +public class WebsocketConnection extends WebSocketClient implements XConnection, XConnectionBroadcaster { + /** + * When set to true, enables various debugging output. + */ + public static final boolean DEBUG = false; + static final byte[] outgoingPrefix = { 'u', 'r', 'p', ' ' }; + + protected String _description; + protected InputStream _inputStream; + protected OutputStream _outputStream; + + protected InputStream _outputStreamReader; + protected OutputStream _inputStreamWriter; + + protected ArrayList _listeners; + + /** + * Constructs a new WebsocketConnection. + * + * @param description the description of the connection. + * @param desc the websocket ConnectionDescriptor containing information such as the websocket URL + */ + public WebsocketConnection(String description, ConnectionDescriptor desc) throws IOException, URISyntaxException, InterruptedException { + super(new URI(desc.getURL())); + + if (DEBUG) System.err.println("##### " + getClass().getName() + " - instantiated " + description + " " + desc); + + _description = description; + + PipedOutputStream inputStreamWriter = new PipedOutputStream(); + PipedInputStream inputPipe = new PipedInputStream(inputStreamWriter); + PipedOutputStream outputPipe = new PipedOutputStream(); + PipedInputStream outputStreamReader = new PipedInputStream(outputPipe); + + + _inputStream = new BufferedInputStream(inputPipe); + _inputStreamWriter = inputStreamWriter; + _outputStream = new BufferedOutputStream(outputPipe); + _outputStreamReader = outputStreamReader; + + _listeners = new ArrayList(); + + connectBlocking(); + } + + public void addStreamListener(XStreamListener aListener ) + throws com.sun.star.uno.RuntimeException { + _listeners.add(aListener); + } + + public void removeStreamListener(XStreamListener aListener ) + throws com.sun.star.uno.RuntimeException { + _listeners.remove(aListener); + } + + private void notifyListeners_open() { + for (XStreamListener xStreamListener : _listeners) { + xStreamListener.started(); + } + } + + private void notifyListeners_close() { + for (XStreamListener xStreamListener : _listeners) { + xStreamListener.closed(); + } + } + + private void notifyListeners_error(com.sun.star.uno.Exception exception) { + for (XStreamListener xStreamListener : _listeners) { + xStreamListener.error(exception); + } + } + + /** + * Read the required number of bytes. + * + * @param bytes the outparameter, where the bytes have to be placed. + * @param nBytesToRead the number of bytes to read. + * @return the number of bytes read. + * + * @see com.sun.star.connection.XConnection#read + */ + public int read(/*OUT*/byte[][] bytes, int nBytesToRead) + throws com.sun.star.io.IOException, com.sun.star.uno.RuntimeException { + + String errMessage = null; + + int read_bytes = 0; + bytes[0] = new byte[nBytesToRead]; + + try { + _inputStreamWriter.flush(); + + int count ; + + do { + count = _inputStream.read(bytes[0], read_bytes, nBytesToRead - read_bytes); + if(count == -1) + errMessage = "EOF reached - " + getDescription(); + + read_bytes += count; + } + while(read_bytes >= 0 && read_bytes < nBytesToRead && count >= 0); + } catch(IOException ioException) { + if(DEBUG) { + System.err.println("##### " + getClass().getName() + ".read - exception occurred:" + ioException); + ioException.printStackTrace(); + } + + errMessage = ioException.toString(); + } + + if(errMessage != null) { + com.sun.star.io.IOException unoIOException = new com.sun.star.io.IOException(errMessage); + notifyListeners_error(unoIOException); + + throw unoIOException; + } + + if (DEBUG) System.err.println(String.format("##### %s - read %s bytes of %s requested", getClass().getName(), Integer.toString(read_bytes), Integer.toString(nBytesToRead))); + + return read_bytes; + } + + /** + * Write bytes. + * + * @param aData the bytes to write. + * @see com.sun.star.connection.XConnection#write + */ + public void write(byte aData[]) throws com.sun.star.io.IOException, + com.sun.star.uno.RuntimeException { + try { + _outputStream.write(aData); + } catch(IOException ioException) { + com.sun.star.io.IOException unoIOException = new com.sun.star.io.IOException(ioException); + notifyListeners_error(unoIOException); + + throw unoIOException; + } + + if (DEBUG) System.err.println(String.format("##### %s - wrote %s bytes", getClass().getName(), Integer.toString(aData.length))); + } + + /** + * Sends the data over the websocket to whatever is on the other side. + * + * **NOTE**: unlike with genuine streams, without flushing the data is + * never sent + * + * @see com.sun.star.connection.XConnection#flush + */ + public void flush() throws com.sun.star.io.IOException, + com.sun.star.uno.RuntimeException { + try { + _outputStream.flush(); + + Integer available = _outputStreamReader.available(); + + byte[] outputBytes = new byte[available + outgoingPrefix.length]; + System.arraycopy(outgoingPrefix, 0, outputBytes, 0, outgoingPrefix.length); + + _outputStreamReader.read(outputBytes, outgoingPrefix.length, available); + + send(outputBytes); + } catch(IOException ioException) { + com.sun.star.io.IOException unoIOException = new com.sun.star.io.IOException(ioException); + notifyListeners_error(unoIOException); + + throw unoIOException; + } + + if (DEBUG) + System.err.println(String.format("##### %s - flushed", getClass().getName())); + } + + /** + * Closes the connection. + * + * @see com.sun.star.connection.XConnection#close + */ + public void close() throws com.sun.star.uno.RuntimeException { + if (DEBUG) System.err.println("##### " + getClass().getName() + " - socket closed"); + } + + /** + * Gives a description of the connection. + * + * @return the description. + * @see com.sun.star.connection.XConnection#getDescription + */ + public String getDescription() throws com.sun.star.uno.RuntimeException { + return _description; + } + + @Override + public void onOpen(ServerHandshake handshakedata) { + notifyListeners_open(); + } + + @Override + public void onClose(int code, String reason, boolean remote) { + notifyListeners_close(); + } + + @Override + public void onMessage(String message) { + String[] messageParts = message.split(": ", 2); + if (messageParts.length != 2) + { + notifyListeners_error(new com.sun.star.uno.Exception(new ProtocolException(String.format("Recieved URP/WS message (%s) without a type specifier. Messages must be proceeded by 'urp: '", message)))); + return; + } + + String messageType = messageParts[0]; + + if (!messageType.equals("urp")) + { + if (DEBUG) System.err.println(String.format("##### %s - received %s message but that is not URP", getClass().getName(), messageType)); + return; + } + + byte[] messageBytes = messageParts[1].getBytes(); + + try { + _inputStreamWriter.write(messageBytes); + } catch (IOException e) { + notifyListeners_error(new com.sun.star.uno.Exception(e)); + return; + } + + if (DEBUG) System.err.println(String.format("##### %s - recieved %s chars", getClass().getName(), Integer.toString(messageBytes.length))); + } + + @Override + public void onMessage(ByteBuffer message) { + byte[] prefixedMessageBytes = message.array(); + + String messageType = ""; + boolean hasType = false; + int i; + for (i = 0; i < prefixedMessageBytes.length - 1; i++) { + if (prefixedMessageBytes[i] == ':' && (prefixedMessageBytes[i+1] == ' ' || prefixedMessageBytes[i+1] == '\n')) { + hasType = true; + break; // The type ends with ": ", so if we find this sequence we found the end of our type + } + messageType += (char)prefixedMessageBytes[i]; + } + + if(!hasType) { + notifyListeners_error(new com.sun.star.uno.Exception(new ProtocolException(String.format("Recieved URP/WS message (%s) without a type specifier. Binary messages must be proceeded by 'urp: ' or 'urp:\\n'", message)))); + return; + } + + int messageStartIndex = i + 2; + + if (!messageType.equals("urp")) { + if (DEBUG) System.err.println(String.format("##### %s - recieved %s binary message but that is not URP", getClass().getName(), messageType)); + return; + } + + byte[] messageBytes = Arrays.copyOfRange(prefixedMessageBytes, messageStartIndex, prefixedMessageBytes.length); + + try { + _inputStreamWriter.write(messageBytes); + } catch (IOException e) { + notifyListeners_error(new com.sun.star.uno.Exception(e)); + return; + } + + if (DEBUG) System.err.println(String.format("##### %s - recieved %s bytes", getClass().getName(), Integer.toString(prefixedMessageBytes.length))); + } + + @Override + public void onError(Exception ex) { + notifyListeners_error(new com.sun.star.uno.Exception(ex)); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/connections/websocket/websocketConnector.java b/ridljar/com/sun/star/lib/connections/websocket/websocketConnector.java new file mode 100644 index 000000000000..a40bb0093c4d --- /dev/null +++ b/ridljar/com/sun/star/lib/connections/websocket/websocketConnector.java @@ -0,0 +1,137 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package com.sun.star.lib.connections.websocket; + +import com.sun.star.comp.loader.FactoryHelper; +import com.sun.star.connection.ConnectionSetupException; +import com.sun.star.connection.NoConnectException; +import com.sun.star.connection.XConnection; +import com.sun.star.connection.XConnector; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; +import com.sun.star.registry.XRegistryKey; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.URISyntaxException; +import java.net.UnknownHostException; + +/** + * A component that implements the XConnector interface. + * + *

The websocketConnector is a specialized component that uses + * websockets for communication. The websocketConnector is generally + * used by the com.sun.star.connection.Connector service.

+ * + * @see com.sun.star.connection.XAcceptor + * @see com.sun.star.connection.XConnection + * @see com.sun.star.connection.XConnector + * @see com.sun.star.comp.loader.JavaLoader + */ +public final class websocketConnector implements XConnector { + /** + * The name of the service. + * + *

The JavaLoader accesses this through reflection.

+ * + * @see com.sun.star.comp.loader.JavaLoader + */ + public static final String __serviceName + = "com.sun.star.connection.websocketConnector"; + + /** + * Returns a factory for creating the service. + * + *

This method is called by the JavaLoader.

+ * + * @param implName the name of the implementation for which a service is + * requested. + * @param multiFactory the service manager to be used (if needed). + * @param regKey the registry key. + * @return an XSingleServiceFactory for creating the component. + * + * @see com.sun.star.comp.loader.JavaLoader + */ + public static XSingleServiceFactory __getServiceFactory( + String implName, XMultiServiceFactory multiFactory, XRegistryKey regKey) + { + return implName.equals(websocketConnector.class.getName()) + ? FactoryHelper.getServiceFactory(websocketConnector.class, + __serviceName, multiFactory, + regKey) + : null; + } + + /** + * Connects via the described websocket to a waiting server. + * + *

The connection description has the following format: + * type*(key=value), + * where type should be websocket + * (ignoring case). Supported keys (ignoring case) currently are

+ *
+ *
url + *
The URL the websocket server is listening on, starting with + * either ws:// or wss:// + *
+ * + * @param connectionDescription the description of the connection. + * @return an XConnection to the server. + * + * @see com.sun.star.connection.XAcceptor + * @see com.sun.star.connection.XConnection + */ + public synchronized XConnection connect(String connectionDescription) + throws NoConnectException, ConnectionSetupException + { + if (connected) + throw new ConnectionSetupException("Already connected to the socket"); + + ConnectionDescriptor desc; + try { + desc = new ConnectionDescriptor(connectionDescription); + } catch (com.sun.star.lang.IllegalArgumentException e) { + throw new ConnectionSetupException(e); + } + + WebsocketConnection websocket = null; + try { + websocket = new WebsocketConnection(connectionDescription, desc); + connected = websocket.isOpen(); + } catch (IOException e) { + throw new ConnectionSetupException(e); + } catch (URISyntaxException e) { + throw new ConnectionSetupException(e); + } catch (InterruptedException e) { + throw new ConnectionSetupException(e); + } + + if (websocket == null || !connected) + throw new ConnectionSetupException("Could not connect to the server. Is it up?"); + + return websocket; + } + + private boolean connected = false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/source/libreoffice/module-info.java b/ridljar/source/libreoffice/module-info.java index 8d24c7ccb13c..f913597600db 100644 --- a/ridljar/source/libreoffice/module-info.java +++ b/ridljar/source/libreoffice/module-info.java @@ -64,6 +64,7 @@ module org.libreoffice.uno exports com.sun.star.ldap; exports com.sun.star.lib.connections.pipe; exports com.sun.star.lib.connections.socket; + exports com.sun.star.lib.connections.websocket; exports com.sun.star.lib.uno; exports com.sun.star.lib.uno.adapter; exports com.sun.star.lib.uno.bridges.java_remote; diff --git a/ridljar/util/manifest b/ridljar/util/manifest index bb1209f90a22..44eb59ff784b 100644 --- a/ridljar/util/manifest +++ b/ridljar/util/manifest @@ -24,6 +24,9 @@ Sealed: true Name: com/sun/star/lib/connections/socket/ Sealed: true +Name: com/sun/star/lib/connections/websocket/ +Sealed: true + Name: com/sun/star/lib/uno/ Sealed: true diff --git a/ure/source/README b/ure/source/README index d66b70003681..da003caacd81 100644 --- a/ure/source/README +++ b/ure/source/README @@ -74,6 +74,7 @@ ELF platforms (Linux, Solaris, *BSD): /opt/libreoffice/ure/lib/libstocserviceslo.so [private] /opt/libreoffice/ure/lib/libuuresolverlo.so [private] /opt/libreoffice/ure/share/java/java_uno.jar [private] +/opt/libreoffice/ure/share/java/java_websocket.jar [private] /opt/libreoffice/ure/share/misc/javavendors.xml [private] Windows: @@ -132,6 +133,7 @@ Program Files\URE\bin\stocserviceslo.dll [private] Program Files\URE\bin\uuresolverlo.dll [private] Program Files\URE\bin\uwinapi.dll [private] Program Files\URE\java\java_uno.jar [private] +Program Files\URE\java\java_websocket.jar [private] Program Files\URE\misc\javavendors.xml [private] %windir%\assembly\cli_basetypes.dll [GAC]