diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 63cb347c1..60a9806fb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,5 +1,5 @@ --- -name: Bug report +name: Bug report / request for enhancement about: Create a report to help us improve junixsocket title: '' labels: '' @@ -8,7 +8,7 @@ assignees: '' --- **Describe the bug** -A clear and concise description of what the bug is. +A clear and concise description of what the bug is or how you'd like to see junixsocket be improved. **To Reproduce** Steps to reproduce the behavior: @@ -22,15 +22,15 @@ A clear and concise description of what you expected to happen. **Output/Screenshots** If applicable, add console output/screenshots to help explain your problem. -**Environment (please complete the following information):** - - OS: [e.g., Linux] - - Distribution [e.g., Alpine] - - Version [e.g., 3.13.0] -Please also add the output of `java -jar junixsocket-selftest-x.y.z-SNAPSHOT-jar-with-dependencies.jar` (x.y.z being the latest version, e.g., 2.3.3). — The selftest jar is available from the junixsocket-dist package in the Release section. +**Please make sure to test the problem still occurs on the latest version of junixsocket** +Please add the output of the selftest from the latest version available, for example: -**Notes** -Add any other context about the problem here. Please link/attach any source code that is useful to diagnose the issue. +`java -jar junixsocket-selftest-2.7.0-jar-with-dependencies.jar` + +The selftest is available from the [Release section](https://github.com/kohlschutter/junixsocket/releases) on GitHub. -**Lastly, please make sure to test the problem still occurs on the latest version of junixsocket** All minor version updates (e.g., 2.4.x -> 2.5.x) are supposed to be backwards compatible. If you find that this isn't the case, please mention it in your report. Thank you! + +**Notes** +Add any other context about the problem here. Please link/attach any source code that is useful to diagnose the issue. diff --git a/.github/workflows/codeql-analysis-c.yml b/.github/workflows/codeql-analysis-c.yml deleted file mode 100644 index a5e642ac9..000000000 --- a/.github/workflows/codeql-analysis-c.yml +++ /dev/null @@ -1,60 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL C" - -on: - push: - branches: [ "main", "dev", "wip" ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ "main", "dev", "wip" ] - schedule: - - cron: '39 16 * * 3' - -jobs: - analyze: - name: Analyze C - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: true - matrix: - language: [ 'cpp' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - - run: | - echo "Build junixsocket-native with Maven (no tests)" - mvn clean install -Pcodeql -pl junixsocket-native -Dignorant - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/codeql-analysis-java.yml b/.github/workflows/codeql-analysis.yml similarity index 81% rename from .github/workflows/codeql-analysis-java.yml rename to .github/workflows/codeql-analysis.yml index 94cee34e2..57ac56ef0 100644 --- a/.github/workflows/codeql-analysis-java.yml +++ b/.github/workflows/codeql-analysis.yml @@ -9,7 +9,7 @@ # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # -name: "CodeQL Java" +name: "CodeQL" on: push: @@ -22,7 +22,7 @@ on: jobs: analyze: - name: Analyze Java + name: Analyze runs-on: ubuntu-latest permissions: actions: read @@ -32,17 +32,26 @@ jobs: strategy: fail-fast: true matrix: - language: [ 'java' ] + language: [ 'cpp', 'java' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: | + 8 + 21 + cache: 'maven' # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -54,7 +63,7 @@ jobs: - run: | echo "Build junixsocket with Maven (no tests)" - mvn clean install -Pcodeql -Pcodeql-skip-c + mvn clean install -Dcodeql -Dstrict -Duse-snapshots - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.gitignore b/.gitignore index 930253cae..988056e0f 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,8 @@ interpolated-pom.xml .checkstyle hs_err_pid*.log .idea +/*.dll +/*.so +/jux.bat +infer-out +javacore.*.txt diff --git a/BUILDING.md b/BUILDING.md new file mode 100644 index 000000000..95fbe4342 --- /dev/null +++ b/BUILDING.md @@ -0,0 +1,33 @@ +# Build instructions + +Details see `src/site/markdown/building.md` or [online here](https://kohlschutter.github.io/junixsocket/building.html) + +## TL;DR. I just want to recompile some Java classes + +To get started with building from source, you really only need: + +- Java 17 or newer +- Maven 3.8.8 or newer +- clang (for the native library). Note that building the library doesn't work from Windows (use WSL2 with Linux). + +Then try one the following commands: + +##### Build everything, but only for the current architecture (not a full release build) + + mvn clean install + +##### Build everything, except for the native library (recommended) + + mvn clean install -rf :junixsocket-common + +#### Build everything, except for the native library and Java 7 support + + mvn clean install -Dretrolambda=false -rf :junixsocket-common + +#### ... and I really don't care about code quality, just build really quick + + mvn clean install -Dretrolambda=false -rf :junixsocket-common -Dignorant + +#### ... nor do I care about test results + + mvn clean install -Dretrolambda=false -rf :junixsocket-common -Dignorant -DskipTests diff --git a/NOTICE b/NOTICE index b9b917486..8930fdb3f 100644 --- a/NOTICE +++ b/NOTICE @@ -1,6 +1,6 @@ junixsocket -Copyright 2009-2022 Christian Kohlschütter +Copyright 2009-2024 Christian Kohlschütter Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 9c6307400..222bfd7c1 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,25 @@ +![junixsocket logo](https://user-images.githubusercontent.com/822690/246675372-d1775152-5f5e-4576-8f3d-8445779ea584.png) + +[![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/kohlschutter/junixsocket/codeql-analysis.yml?cacheSeconds=60)](https://github.com/kohlschutter/junixsocket/actions/workflows/codeql-analysis.yml) [![Last commit on main](https://img.shields.io/github/last-commit/kohlschutter/junixsocket/main)](https://github.com/kohlschutter/junixsocket/commits/main) [![Maven Central version](https://img.shields.io/maven-central/v/com.kohlschutter.junixsocket/junixsocket)](https://search.maven.org/artifact/com.kohlschutter.junixsocket/junixsocket) [![Apache 2.0 Licensed](https://img.shields.io/github/license/kohlschutter/junixsocket)](https://github.com/kohlschutter/junixsocket/blob/main/NOTICE) + +**Users of junixsocket are strongly advised to upgrade to version 2.10.0 or newer ([changelog](https://kohlschutter.github.io/junixsocket/changelog.html))** + # junixsocket junixsocket is a Java/JNI library that allows the use of [Unix Domain Sockets](https://en.wikipedia.org/wiki/Unix_domain_socket) (AF_UNIX sockets), and -other address/protocol families (such as [AF_TIPC](http://tipc.io/) and AF_VSOCK), from Java. +other address/protocol families (such as [AF_TIPC](http://tipc.io/), AF_VSOCK, and AF_SYSTEM), +from Java. ## Unix sockets API, in Java, AF. * *junixsocket* is the most complete implementation of AF_UNIX sockets for the Java ecosystem. -* Supports other socket types, such as TIPC (on Linux) and VSOCK (on Linux, and certain macOS VMs), as well! +* Supports other socket types, such as TIPC (on Linux), VSOCK (on Linux, and certain macOS VMs), and + AF_SYSTEM (on macOS) as well! * Comes with pre-built native libraries for most operating systems and platforms, including - macOS, Linux, Windows, Solaris, FreeBSD, NetBSD, OpenBSD, DragonFlyBSD, AIX, IBM i. + macOS, Linux, Android, Windows, Solaris, FreeBSD, NetBSD, OpenBSD, DragonFlyBSD, AIX, IBM i. * Additionally, you can build and run junixsocket natively on IBM z/OS (experimental). -* Supports all Java versions since Java 8* +* Supports all Java versions since Java 8* (with common AF_UNIX support available for Java 7 and newer) * Supports both the Java Socket API and NIO (`java.net.Socket`, `java.net.SocketChannel`, etc.) * Supports streams and datagrams. * Supports Remote Method Invocation (RMI) over AF_UNIX. @@ -25,10 +33,11 @@ other address/protocol families (such as [AF_TIPC](http://tipc.io/) and AF_VSOCK * Supports [HTTP over UNIX sockets](https://kohlschutter.github.io/junixsocket/http.html) (using [NanoHTTPD](https://github.com/NanoHttpd/nanohttpd), [OkHttp](https://github.com/square/okhttp), and [jetty](https://github.com/eclipse/jetty.project/)). * Supports JPMS/Jigsaw modules. The project is modularized so you can install only what you need. * Supports GraalVM native-image AOT/ahead-of-time compilation (since 2.6.0) -* Provides a selftest package with 100+ tests to ensure compatibility with any target platform. +* Provides a selftest package with 300+ tests to ensure compatibility with any target platform. +* No outside dependencies are necessary at runtime. * Apache 2.0 licensed. -`*` (Tested up to Java 19; support for Java 7 was dropped in version 2.5.0). +`*` (Tested up to Java 24; basic support for Java 7 was dropped in version 2.5.0 and reintroduced in version 2.8.0). ## Quick links @@ -38,11 +47,18 @@ other address/protocol families (such as [AF_TIPC](http://tipc.io/) and AF_VSOCK * [Demo code](https://kohlschutter.github.io/junixsocket/demo.html) ([Java source](https://kohlschutter.github.io/junixsocket/junixsocket-demo/xref/index.html)) - Sockets (`org.newsclub.net.unix.demo`) - RMI over Unix Sockets (`org.newsclub.net.unix.demo.rmi` and `org.newsclub.net.unix.demo.rmi.services`) - - MySQL over Unix Sockets (`org.newsclub.net.mysql.demo`) + - MySQL over Unix Sockets (`org.newsclub.net.mysql.demo`) + - Postgres over Unix Sockets (`org.newsclub.net.unix.demo.jdbc`) + - Apache Mina (`org.newsclub.net.unix.demo.mina`) + - NanoHttpd (`org.newsclub.net.unix.demo.nanohttpd`) + - Netty (`org.newsclub.net.unix.demo.netty`) + - OkHttp (`org.newsclub.net.unix.demo.okhttp`) + - SSL (`org.newsclub.net.unix.demo.ssl`) * [API Javadocs](https://kohlschutter.github.io/junixsocket/apidocs/) * [Unix Domain Socket Reference](https://kohlschutter.github.io/junixsocket/unixsockets.html) * [TIPC documentation](https://kohlschutter.github.io/junixsocket/junixsocket-tipc/index.html) * [VSOCK documentation](https://kohlschutter.github.io/junixsocket/junixsocket-vsock/index.html) + * [AF_SYSTEM documentation](https://kohlschutter.github.io/junixsocket/junixsocket-darwin/index.html) ## Licensing @@ -66,23 +82,28 @@ java -jar junixsocket-selftest-VERSION-jar-with-dependencies.jar To include the core junixsocket functionality in your project, add the following Maven dependency -> **NOTE** Since version 2.4.0, `junixsocket-core` is POM-only (that's why you need to specify `pom`) +> **NOTE** Since version 2.4.0, `junixsocket-core` is POM-only (that's why you need to specify +`pom`) ``` com.kohlschutter.junixsocket junixsocket-core - 2.6.1 + 2.10.0 pom ``` -While you should definitely pin your dependency to a specific version, you are very much encouraged to always update to the most recent version. Check back frequently. +While you should definitely pin your dependency to a specific version, you are very much encouraged +to keep updating to the most recent version. Check back frequently. -For more, optional packages (RMI, MySQL, Jetty, TIPC, VSOCK, server, GraalVM, etc.) and Gradle instructions see -[here](https://kohlschutter.github.io/junixsocket/dependency.html) +For more, optional packages (RMI, MySQL, Jetty, TIPC, VSOCK, server, Darwin, SSL, GraalVM, etc.) and +Gradle instructions see [here](https://kohlschutter.github.io/junixsocket/dependency.html) -If you're testing a `-SNAPSHOT` version, make sure that the Sonatype snapshot repository is enabled in your POM: +## Snapshot builds for testing + +When you're testing a `-SNAPSHOT` version, make sure that the Sonatype snapshot repository is +enabled in your POM: ``` @@ -98,4 +119,15 @@ If you're testing a `-SNAPSHOT` version, make sure that the Sonatype snapshot re ``` -> **NOTE** Never rely on -SNAPSHOT builds. They can break any time. +To update to the latest SNAPSHOT (which is currently not being built for every commit), +run the following command from within your own project: + +``` +mvn -U dependency:resolve +``` + +or (for Gradle) + +``` +./gradlew refreshVersions +``` diff --git a/SECURITY.md b/SECURITY.md index 084108330..8aee1dc98 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -9,14 +9,17 @@ possible. Should a security issue arise, a fix would usually be included in a PATCH or MINOR version update, i.e., 2.6.1 -> 2.6.2 or 2.7.0. +All 2.x.x versions should be backwards-compatible. Consider the latest 2.x.x version the recommended +version. + If you depend on an outdated version of junixsocket or need other commercial support, please reach out to directly to Christian Kohlschütter (email is in `pom.xml`). -| Version | Supported | -| ------- | ------------------ | -| 2.6.x | :white_check_mark: | -| < 2.6.x | Commercial support available | -| < 2.0 | :x: | +| Version | Supported | +| -------- | ------------------ | +| 2.10.x | :white_check_mark: | +| < 2.10.x | Commercial support available | +| < 2.0 | :x: | ## Reporting a Vulnerability diff --git a/docs b/docs new file mode 120000 index 000000000..3590d9f93 --- /dev/null +++ b/docs @@ -0,0 +1 @@ +src/site/markdown \ No newline at end of file diff --git a/junixsocket-codecoverage/jacoco-extra/.gitignore b/junixsocket-codecoverage/jacoco-extra/.gitignore new file mode 100644 index 000000000..f6a7da6fc --- /dev/null +++ b/junixsocket-codecoverage/jacoco-extra/.gitignore @@ -0,0 +1,2 @@ +lib/ +*.zip diff --git a/junixsocket-codecoverage/jacoco-extra/README b/junixsocket-codecoverage/jacoco-extra/README new file mode 100644 index 000000000..739fee447 --- /dev/null +++ b/junixsocket-codecoverage/jacoco-extra/README @@ -0,0 +1,26 @@ +Place additional jacoco *.exec files here to improve code coverage reporting. + +Make sure to delete all outdated *.exec files first. + +Collect code coverage results by running junixsocket-selftest on all supported +target platforms and configurations. Make sure to test in environments with +certain features enabled/disabled and configured differently (e.g., TIPC +enabled but not configured). + +Run the following command to collect these *.exec files on a target system: +(requires "hostname" and "uuidgen" to work as commands) + +./run-selftest-coverage.sh +(this may download the JaCoCo agent jar if it's not already there) + +The test files will be stored under junixsocket-codecoverage/target. + +Depending on your setup (shared directory or not), you may now need to +aggregate these *.exec files. + +Then, on the machine aggregating the results, run + +./rerun-aggregate-coverage.sh + +which will set up the aggregate report under +junixsocket-codecoverage/target/site/jacoco-aggregate/index.html diff --git a/junixsocket-codecoverage/jacoco-extra/rerun-aggregate-coverage.sh b/junixsocket-codecoverage/jacoco-extra/rerun-aggregate-coverage.sh new file mode 100755 index 000000000..e57d43ae8 --- /dev/null +++ b/junixsocket-codecoverage/jacoco-extra/rerun-aggregate-coverage.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cd "$(dirname $0)"/../.. +mvn verify -Pstrict -Prelease -rf :junixsocket-common -P \!code-quality,\!documentation -DskipTests diff --git a/junixsocket-codecoverage/jacoco-extra/run-selftest-coverage.sh b/junixsocket-codecoverage/jacoco-extra/run-selftest-coverage.sh new file mode 100755 index 000000000..ebc015b8a --- /dev/null +++ b/junixsocket-codecoverage/jacoco-extra/run-selftest-coverage.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +cd $(dirname "$0") + +[[ -n "$JACOCO_VERSION" ]] || JACOCO_VERSION=0.8.10 + +if [[ ! -e "lib/jacocoagent.jar" ]]; then + wget "https://search.maven.org/remotecontent?filepath=org/jacoco/jacoco/${JACOCO_VERSION}/jacoco-${JACOCO_VERSION}.zip" -O jacoco.zip + + unzip jacoco.zip lib/jacocoagent.jar +fi + +mkdir -p ../target + +java \ + -javaagent:./lib/jacocoagent.jar=destfile=../target/jacoco-$(hostname)-$(uuidgen).exec,excludes='org/apache/maven/**:org/junit/**:org/apiguardian/**:sun/**' \ +-jar ../../junixsocket-selftest/target/junixsocket-selftest-*-jar-with-dependencies.jar diff --git a/junixsocket-codecoverage/pom.xml b/junixsocket-codecoverage/pom.xml index 20ba3ea87..ae585c890 100644 --- a/junixsocket-codecoverage/pom.xml +++ b/junixsocket-codecoverage/pom.xml @@ -1,12 +1,14 @@ - + 4.0.0 junixsocket-codecoverage pom com.kohlschutter.junixsocket junixsocket - 2.6.1 + 2.10.1 ../pom.xml junixsocket-codecoverage @@ -59,26 +61,42 @@ com.kohlschutter.junixsocket junixsocket-common - ${project.version} + compile + + + com.kohlschutter.junixsocket + junixsocket-rmi + compile + + + com.kohlschutter.junixsocket + junixsocket-ssl compile com.kohlschutter.junixsocket junixsocket-tipc - ${project.version} + compile + + + com.kohlschutter.junixsocket + junixsocket-vsock compile com.kohlschutter.junixsocket junixsocket-jetty - ${project.version} compile com.kohlschutter.junixsocket - junixsocket-rmi - ${project.version} + junixsocket-jetty-11 + compile + + + com.kohlschutter.junixsocket + junixsocket-darwin compile - \ No newline at end of file + diff --git a/junixsocket-common/pom.xml b/junixsocket-common/pom.xml index 7d4bd1784..538147928 100644 --- a/junixsocket-common/pom.xml +++ b/junixsocket-common/pom.xml @@ -6,15 +6,16 @@ com.kohlschutter.junixsocket junixsocket - 2.6.1 + 2.10.1 ../pom.xml junixsocket-common ${project.parent.basedir} + true - The public, core API + junixsocket is a Java/JNI library that allows the use of Unix Domain Sockets (AF_UNIX sockets) and other socket types, such as AF_TIPC and AF_VSOCK, from Java, using the standard Socket API @@ -25,6 +26,7 @@ -h ${kohlschutter.project.base.directory}/junixsocket-native/src/main/c + ${xlint.compiler.arg} @@ -55,20 +57,39 @@ + + + + with-native-custom + + + !junixsocket.native-custom.skip + + + + + com.kohlschutter.junixsocket + junixsocket-native-custom + ${project.version} + test + default + true + + + + + com.kohlschutter.junixsocket junixsocket-native-common - ${project.version} test + - com.kohlschutter.junixsocket - junixsocket-native-custom - ${project.version} - test - default - true + org.codehaus.mojo + animal-sniffer-annotations + provided diff --git a/junixsocket-common/src/main/java-unresolved-properties/org/newsclub/net/unix/BuildProperties.java b/junixsocket-common/src/main/java-unresolved-properties/org/newsclub/net/unix/BuildProperties.java new file mode 100644 index 000000000..92a830772 --- /dev/null +++ b/junixsocket-common/src/main/java-unresolved-properties/org/newsclub/net/unix/BuildProperties.java @@ -0,0 +1,55 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Properties that are populated upon build time. + * + * Also see {@code src/main/unresolved-java/.../BuildProperties.java} + * + * @author Christian Kohlschütter + */ +final class BuildProperties { + private static final Map MAP; + + static { + Map map = new LinkedHashMap<>(); + + map.put("project.version", "${project.version}"); + map.put("git.build.version", "${git.build.version}"); // junixsocket version + map.put("git.commit.id.abbrev", "${git.commit.id.abbrev}"); + map.put("git.commit.id.describe", "${git.commit.id.abbrev}"); + map.put("git.commit.id.full", "${git.commit.id.full}"); + map.put("git.commit.time", "${git.commit.time}"); + map.put("git.dirty", "${git.dirty}"); + + MAP = Collections.unmodifiableMap(map); + } + + private BuildProperties() { + throw new IllegalStateException("No instances"); + } + + static Map getBuildProperties() { + return MAP; + } +} diff --git a/junixsocket-common/src/main/java/module-info.java b/junixsocket-common/src/main/java/module-info.java index 849cfa966..335e8b7b8 100644 --- a/junixsocket-common/src/main/java/module-info.java +++ b/junixsocket-common/src/main/java/module-info.java @@ -1,11 +1,14 @@ /** * The common junixsocket classes. */ -module org.newsclub.net.unix { +// NOPMD -- https://github.com/pmd/pmd/issues/4620 +@SuppressWarnings("module") module org.newsclub.net.unix { exports org.newsclub.net.unix; requires java.base; requires static java.rmi; + requires static com.kohlschutter.annotations.compiletime; requires static org.eclipse.jdt.annotation; + requires static animal.sniffer.annotations; } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFAddressFamily.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFAddressFamily.java index 61649530b..3df3e5f28 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFAddressFamily.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFAddressFamily.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; +import java.net.SocketAddress; import java.net.SocketException; import java.net.URI; import java.nio.channels.ServerSocketChannel; @@ -39,7 +40,7 @@ /** * Describes an address family supported by junixsocket. - * + * * @param The corresponding {@link AFSocketAddress} subclass. * @author Christian Kohlschütter */ @@ -179,7 +180,7 @@ String getJuxInetAddressSuffix() { /** * Registers an address family. - * + * * @param The supported address type. * @param juxString The sockaddr_* identifier as registered in native code. * @param addressClass The supported address subclass. @@ -196,7 +197,7 @@ public static synchronized AFAddressFamily regist } if (af.addressClassname != null && !addressClass.getName().equals(af.addressClassname)) { throw new IllegalStateException("Unexpected classname for address family " + juxString + ": " - + addressClass.getName()); + + addressClass.getName() + "; expected: " + af.addressClassname); } if (af.addressConstructor != null || af.addressClass != null) { throw new IllegalStateException("Already registered: " + juxString); @@ -226,7 +227,7 @@ public static synchronized AFAddressFamily regist /** * Registers an implementation. - * + * * @param The supported address type. * @param juxString The sockaddr_* identifier as registered in native code. * @param addressFamily The supported address family as registered via @@ -267,6 +268,8 @@ AFSocketImplExtensions initImplExtensions(AncillaryDataSupport ancillaryDataS return (AFSocketImplExtensions) new AFTIPCSocketImplExtensions(ancillaryDataSupport); case NativeUnixSocket.DOMAIN_VSOCK: return (AFSocketImplExtensions) new AFVSOCKSocketImplExtensions(ancillaryDataSupport); + case NativeUnixSocket.DOMAIN_SYSTEM: + return (AFSocketImplExtensions) new AFSYSTEMSocketImplExtensions(ancillaryDataSupport); default: throw new UnsupportedOperationException(); } @@ -274,7 +277,7 @@ AFSocketImplExtensions initImplExtensions(AncillaryDataSupport ancillaryDataS /** * Creates a new, unconnected, unbound socket compatible with this socket address. - * + * * @return The socket instance. * @throws IOException on error. */ @@ -288,7 +291,7 @@ public AFSocket newSocket() throws IOException { /** * Creates a new, unconnected, unbound server socket compatible with this socket address. - * + * * @return The server socket instance. * @throws IOException on error. */ @@ -302,7 +305,7 @@ public AFServerSocket newServerSocket() throws IOException { /** * Creates a new, unconnected, unbound {@link SocketChannel} compatible with this socket address. - * + * * @return The socket instance. * @throws IOException on error. */ @@ -313,7 +316,7 @@ public AFSocketChannel newSocketChannel() throws IOException { /** * Creates a new, unconnected, unbound {@link ServerSocketChannel} compatible with this socket * address. - * + * * @return The socket instance. * @throws IOException on error. */ @@ -330,10 +333,10 @@ AFSocketAddress parseURI(URI u, int overridePort) throws SocketException { /** * Returns the set of supported URI schemes that can be parsed to some {@link AFSocketAddress}. - * + * * The set is dependent on which {@link AFSocketAddress} implementations are registered with * junixsocket. - * + * * @return The set of supported URI schemes. */ public static synchronized Set uriSchemes() { @@ -344,7 +347,7 @@ public static synchronized Set uriSchemes() { /** * Returns the {@link SelectorProvider} associated with this address family, or {@code null} if no * such instance is registered. - * + * * @return The {@link SelectorProvider}. * @throws IllegalStateException on error. */ @@ -357,7 +360,7 @@ public synchronized SelectorProvider getSelectorProvider() { } try { selectorProvider = (SelectorProvider) Class.forName(selectorProviderClassname).getMethod( - "provider", new Class[0]).invoke(null); + "provider", new Class[0]).invoke(null); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException | RuntimeException e) { throw new IllegalStateException("Cannot instantiate selector provider for " @@ -365,4 +368,14 @@ public synchronized SelectorProvider getSelectorProvider() { } return selectorProvider; } + + /** + * Returns an appropriate SocketAddress to be used when calling bind with a null argument. + * + * @return The new socket address, or {@code null}. + * @throws IOException on error. + */ + public SocketAddress nullBindAddress() throws IOException { + return addressConfig.nullBindAddress(); + } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFAddressFamilyConfig.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFAddressFamilyConfig.java index 88f4a5290..0f2f53c47 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFAddressFamilyConfig.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFAddressFamilyConfig.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ /** * The implementation-specifics for a given address family implementation. - * + * * @param The supported address type. * @author Christian Kohlschütter * @see AFSocketAddressConfig @@ -41,63 +41,63 @@ protected AFAddressFamilyConfig() { /** * Returns the implementation's {@link Socket} class. - * + * * @return The implementation's {@link Socket} class. */ protected abstract Class> socketClass(); /** * Returns the implementation's {@link Socket} constructor. - * + * * @return The implementation's {@link Socket} constructor. */ protected abstract AFSocket.Constructor socketConstructor(); /** * Returns the implementation's {@link ServerSocket} class. - * + * * @return The implementation's {@link ServerSocket} class. */ protected abstract Class> serverSocketClass(); /** * Returns the implementation's {@link ServerSocket} constructor. - * + * * @return The implementation's {@link ServerSocket} constructor. */ protected abstract AFServerSocket.Constructor serverSocketConstructor(); /** * Returns the implementation's {@link SocketChannel} class. - * + * * @return The implementation's {@link SocketChannel} class.. */ protected abstract Class> socketChannelClass(); /** * Returns the implementation's {@link ServerSocketChannel} class. - * + * * @return The implementation's {@link ServerSocketChannel} class. */ protected abstract Class> serverSocketChannelClass(); /** * Returns the implementation's {@link DatagramSocket} class. - * + * * @return The implementation's {@link DatagramSocket} class. */ protected abstract Class> datagramSocketClass(); /** * Returns the implementation's {@link DatagramSocket} constructor. - * + * * @return The implementation's {@link DatagramSocket} constructor. */ protected abstract AFDatagramSocket.Constructor datagramSocketConstructor(); /** * Returns the implementation's {@link DatagramChannel} class. - * + * * @return The implementation's {@link DatagramChannel} class. */ protected abstract Class> datagramChannelClass(); diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFCore.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFCore.java index 0b67705ad..87fd6ac97 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFCore.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFCore.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,16 +21,39 @@ import java.io.IOException; import java.net.SocketAddress; import java.net.SocketException; +import java.net.SocketTimeoutException; import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousCloseException; +import java.nio.channels.ClosedByInterruptException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.SelectionKey; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.jdt.annotation.NonNull; +import org.newsclub.net.unix.pool.MutableHolder; +import org.newsclub.net.unix.pool.ObjectPool; +import org.newsclub.net.unix.pool.ObjectPool.Lease; + +import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; /** * The core functionality of file descriptor based I/O. - * + * * @author Christian Kohlschütter */ class AFCore extends CleanableState { - private static final ThreadLocal TL_BUFFER = new ThreadLocal<>(); + private static final ObjectPool> TL_BUFFER = ObjectPool + .newThreadLocalPool(() -> { + return new MutableHolder<>(null); + }, (o) -> { + ByteBuffer bb = o.get(); + if (bb != null) { + bb.clear(); + } + return true; + }); private static final String PROP_TL_BUFFER_MAX_CAPACITY = "org.newsclub.net.unix.thread-local-buffer.max-capacity"; // 0 means "no limit" (discouraged) @@ -46,14 +69,17 @@ class AFCore extends CleanableState { private final boolean datagramMode; - private boolean blocking = true; + private final AtomicInteger virtualBlockingLeases = new AtomicInteger(0); + private volatile boolean blocking = true; + private boolean cleanFd = true; AFCore(Object observed, FileDescriptor fd, AncillaryDataSupport ancillaryDataSupport, boolean datagramMode) { super(observed); this.datagramMode = datagramMode; - this.fd = (fd == null) ? new FileDescriptor() : fd; this.ancillaryDataSupport = ancillaryDataSupport; + + this.fd = fd == null ? new FileDescriptor() : fd; } AFCore(Object observed, FileDescriptor fd) { @@ -61,8 +87,8 @@ class AFCore extends CleanableState { } @Override - protected void doClean() { - if (fd != null && fd.valid()) { + protected final void doClean() { + if (fd != null && fd.valid() && cleanFd) { try { doClose(); } catch (IOException e) { @@ -74,13 +100,18 @@ protected void doClean() { } } + void disableCleanFd() { + this.cleanFd = false; + } + boolean isClosed() { return closed.get(); } void doClose() throws IOException { - NativeUnixSocket.close(fd); - closed.set(true); + if (closed.compareAndSet(false, true)) { + NativeUnixSocket.close(fd); + } } FileDescriptor validFdOrException() throws SocketException { @@ -105,53 +136,138 @@ synchronized FileDescriptor validFd() { return null; } - int read(ByteBuffer dst) throws IOException { - return read(dst, null, 0); + int read(ByteBuffer dst, AFSupplier timeout) throws IOException { + return read(dst, timeout, null, 0); } - int read(ByteBuffer dst, ByteBuffer socketAddressBuffer, int options) throws IOException { + @SuppressWarnings({ + "PMD.NcssCount", "PMD.CognitiveComplexity", "PMD.CyclomaticComplexity", + "PMD.NPathComplexity"}) + @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") + int read(ByteBuffer dst, AFSupplier timeout, ByteBuffer socketAddressBuffer, int options) + throws IOException { int remaining = dst.remaining(); if (remaining == 0) { return 0; } FileDescriptor fdesc = validFdOrException(); + int dstPos = dst.position(); + ByteBuffer buf; - if (dst.isDirect()) { - buf = dst; + int pos; + + boolean direct = dst.isDirect(); + + final boolean virtualBlocking = (ThreadUtil.isVirtualThread() && isBlocking()) + || isVirtualBlocking(); + final long now; + if (virtualBlocking) { + now = System.currentTimeMillis(); } else { - buf = getThreadLocalDirectByteBuffer(remaining); - remaining = Math.min(remaining, buf.remaining()); + now = 0; } - - if (!blocking) { + if (virtualBlocking || !blocking) { options |= NativeUnixSocket.OPT_NON_BLOCKING; } - int pos = dst.position(); + boolean park = false; - int count = NativeUnixSocket.receive(fdesc, buf, pos, remaining, socketAddressBuffer, options, - ancillaryDataSupport, 0); - if (count == -1) { - return count; - } - if (buf != dst) { // NOPMD - buf.limit(count); - dst.put(buf); - } else { - if (count < 0) { - throw new IllegalStateException(); + int count; + virtualThreadLoop : do { + if (virtualBlocking) { + if (park) { + VirtualThreadPoller.INSTANCE.parkThreadUntilReady(fdesc, SelectionKey.OP_WRITE, now, + timeout, this::close); + } + configureVirtualBlocking(true); } - dst.position(pos + count); - } + + try (Lease> lease = direct ? null : getPrivateDirectByteBuffer( + remaining)) { + if (direct) { + buf = dst; + pos = dstPos; + } else { + buf = Objects.requireNonNull(Objects.requireNonNull(lease).get().get()); + remaining = Math.min(remaining, buf.remaining()); + pos = buf.position(); + buf.limit(pos + remaining); + } + + try { + count = NativeUnixSocket.receive(fdesc, buf, pos, remaining, socketAddressBuffer, options, + ancillaryDataSupport, 0); + if (count == 0 && virtualBlocking) { + // try again + park = true; + continue virtualThreadLoop; + } + } catch (AsynchronousCloseException e) { + throw e; + } catch (ClosedChannelException e) { + if (isClosed()) { + throw e; + } else if (Thread.currentThread().isInterrupted()) { + throw (ClosedByInterruptException) new ClosedByInterruptException().initCause(e); + } else { + throw (AsynchronousCloseException) new AsynchronousCloseException().initCause(e); + } + } catch (SocketTimeoutException e) { + if (virtualBlocking) { + // try again + park = true; + continue virtualThreadLoop; + } else { + throw e; + } + } + + if (count == -1 || buf == null) { + return -1; + } + + if (direct) { + if (count < 0) { + throw new IllegalStateException(); + } + dst.position(pos + count); + } else { + int oldLimit = buf.limit(); + if (count < oldLimit) { + buf.limit(count); + } + try { + while (buf.hasRemaining()) { + dst.put(buf); + } + } finally { + if (count < oldLimit) { + buf.limit(oldLimit); + } + } + } + } finally { + if (virtualBlocking) { + configureVirtualBlocking(false); + } + } + break; // NOPMD.AvoidBranchingStatementAsLastInLoop virtualThreadLoop + } while (true); // NOPMD.WhileLoopWithLiteralBoolean + return count; } - int write(ByteBuffer src) throws IOException { - return write(src, null, 0); + int write(ByteBuffer src, AFSupplier timeout) throws IOException { + return write(src, timeout, null, 0); } - int write(ByteBuffer src, SocketAddress target, int options) throws IOException { + @SuppressWarnings({ + "PMD.NcssCount", "PMD.CognitiveComplexity", "PMD.CyclomaticComplexity", + "PMD.NPathComplexity"}) + @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") + int write(ByteBuffer src, AFSupplier timeout, SocketAddress target, int options) + throws IOException { int remaining = src.remaining(); if (remaining == 0) { @@ -161,85 +277,166 @@ int write(ByteBuffer src, SocketAddress target, int options) throws IOException FileDescriptor fdesc = validFdOrException(); final ByteBuffer addressTo; final int addressToLen; - if (target == null) { - addressTo = null; - addressToLen = 0; - } else { - addressTo = AFSocketAddress.SOCKETADDRESS_BUFFER_TL.get(); - addressToLen = AFSocketAddress.unwrapAddressDirectBufferInternal(addressTo, target); - } + try (Lease addressToLease = target == null ? null + : AFSocketAddress.SOCKETADDRESS_BUFFER_TL.take()) { + if (addressToLease == null) { + addressTo = null; + addressToLen = 0; + } else { + addressTo = addressToLease.get(); + addressToLen = AFSocketAddress.unwrapAddressDirectBufferInternal(addressTo, target); + } - // accept "send buffer overflow" as packet loss - // and don't retry (which may slow things down quite a bit) - if (!blocking) { - options |= NativeUnixSocket.OPT_NON_BLOCKING; - } + // accept "send buffer overflow" as packet loss + // and don't retry (which may slow things down quite a bit) - int pos = src.position(); + int pos = src.position(); + boolean isDirect = src.isDirect(); + ByteBuffer buf; + int bufPos; - boolean isDirect = src.isDirect(); - ByteBuffer buf; - if (isDirect) { - buf = src; - } else { - buf = getThreadLocalDirectByteBuffer(remaining); - remaining = Math.min(remaining, buf.remaining()); - - // Java 16: buf.put(buf.position(), src, src.position(), Math.min(buf.limit(), src.limit())); - int limit = src.limit(); - if (limit > buf.limit()) { - src.limit(buf.limit()); - buf.put(src); - src.limit(limit); + final boolean virtualBlocking = (ThreadUtil.isVirtualThread() && isBlocking()) + || isVirtualBlocking(); + final long now; + if (virtualBlocking) { + now = System.currentTimeMillis(); } else { - buf.put(src); + now = 0; + } + if (virtualBlocking || !blocking) { + options |= NativeUnixSocket.OPT_NON_BLOCKING; + } + if (datagramMode) { + options |= NativeUnixSocket.OPT_DGRAM_MODE; } - buf.position(0); - } - if (datagramMode) { - options |= NativeUnixSocket.OPT_DGRAM_MODE; + int written; + + boolean park = false; + virtualThreadLoop : do { + if (virtualBlocking) { + if (park) { + VirtualThreadPoller.INSTANCE.parkThreadUntilReady(fdesc, SelectionKey.OP_WRITE, now, + timeout, this::close); + } + configureVirtualBlocking(true); + } + + try (Lease> lease = isDirect ? null : getPrivateDirectByteBuffer( + remaining)) { + if (isDirect) { + buf = src; + bufPos = pos; + } else { + buf = Objects.requireNonNull(Objects.requireNonNull(lease).get().get()); + remaining = Math.min(remaining, buf.remaining()); + + bufPos = buf.position(); + + while (src.hasRemaining() && buf.hasRemaining()) { + buf.put(src); + } + + buf.position(bufPos); + } + + written = NativeUnixSocket.send(fdesc, buf, bufPos, remaining, addressTo, addressToLen, + options, ancillaryDataSupport); + if (written == 0 && virtualBlocking) { + // try again + park = true; + continue virtualThreadLoop; + } + } catch (SocketTimeoutException e) { + if (virtualBlocking) { + // try again + park = true; + continue virtualThreadLoop; + } else { + throw e; + } + } finally { + if (virtualBlocking) { + configureVirtualBlocking(false); + } + } + break; // NOPMD.AvoidBranchingStatementAsLastInLoop virtualThreadLoop + } while (true); // NOPMD.WhileLoopWithLiteralBoolean + src.position(pos + written); + return written; } - - int written = NativeUnixSocket.send(fdesc, buf, pos, remaining, addressTo, addressToLen, - options, ancillaryDataSupport); - src.position(pos + written); - - return written; } /** * Returns a per-thread reusable byte buffer for a given capacity. - * + * * If a thread-local buffer currently uses a smaller capacity, the buffer is replaced by a larger * one. If the capacity exceeds a configurable maximum, a new direct buffer is allocated but not * cached (i.e., the previously cached one is kept but not immediately returned to the caller). - * + * * @param capacity The desired capacity. * @return A byte buffer satisfying the requested capacity. */ - ByteBuffer getThreadLocalDirectByteBuffer(int capacity) { + @SuppressWarnings("null") + Lease> getPrivateDirectByteBuffer(int capacity) { if (capacity > TL_BUFFER_MAX_CAPACITY && TL_BUFFER_MAX_CAPACITY > 0) { // Capacity exceeds configurable maximum limit; // allocate but do not cache direct buffer. // This may incur a performance penalty at the cost of correctness when using such capacities. - return ByteBuffer.allocateDirect(capacity); + return ObjectPool.unpooledLease(new MutableHolder<>(ByteBuffer.allocateDirect(capacity))); } if (capacity < TL_BUFFER_MIN_CAPACITY) { capacity = TL_BUFFER_MIN_CAPACITY; } - ByteBuffer buffer = TL_BUFFER.get(); + Lease> lease = TL_BUFFER.take(); + MutableHolder holder = lease.get(); + ByteBuffer buffer = holder.get(); if (buffer == null || capacity > buffer.capacity()) { buffer = ByteBuffer.allocateDirect(capacity); - TL_BUFFER.set(buffer); + holder.set(buffer); } buffer.clear(); - return buffer; + return lease; } void implConfigureBlocking(boolean block) throws IOException { - NativeUnixSocket.configureBlocking(validFdOrException(), block); this.blocking = block; + if (block && isVirtualBlocking()) { + // do not actually change it here, defer it to when the virtual blocking counter goes to 0 + } else { + NativeUnixSocket.configureBlocking(validFdOrException(), block); + } + } + + /** + * Increments/decrements the "virtual blocking" counter (calls must be in pairs/balanced using + * try-finally blocks). + * + * @param enabled {@code true} if increment, {@code false} if decrement. + * @throws SocketException on error. + * @throws IOException on error, including count overflow/underflow. + */ + void configureVirtualBlocking(boolean enabled) throws SocketException, IOException { + int v; + if (enabled) { + if ((v = this.virtualBlockingLeases.incrementAndGet()) >= 1 && blocking) { + NativeUnixSocket.configureBlocking(validFdOrException(), false); + } + if (v >= Integer.MAX_VALUE) { + throw new IOException("blocking overflow"); + } + } else { + if ((v = this.virtualBlockingLeases.decrementAndGet()) == 0 && blocking) { + NativeUnixSocket.configureBlocking(validFdOrException(), true); + } + if (v < 0) { + throw new IOException("blocking underflow"); + } + } + } + + boolean isVirtualBlocking() { + return virtualBlockingLeases.get() > 0; } boolean isBlocking() { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFDatagramChannel.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFDatagramChannel.java index 665580039..405410cb4 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFDatagramChannel.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFDatagramChannel.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,14 +17,18 @@ */ package org.newsclub.net.unix; +import static java.util.Objects.requireNonNull; + import java.io.FileDescriptor; import java.io.IOException; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.NetworkInterface; +import java.net.ProtocolFamily; import java.net.Socket; import java.net.SocketAddress; import java.net.SocketOption; +import java.net.StandardProtocolFamily; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.nio.channels.MembershipKey; @@ -37,16 +41,17 @@ /** * A {@link DatagramChannel} implementation that works with junixsocket. - * + * * @author Christian Kohlschütter + * @param The supported address type. */ public abstract class AFDatagramChannel extends DatagramChannel - implements AFSomeSocket, AFSocketExtensions { + implements AFSomeSocket, AFSocketExtensions, AFSomeSocketChannel { private final AFDatagramSocket afSocket; /** * Creates a new {@link AFDatagramChannel} instance. - * + * * @param selectorProvider The corresponding {@link SelectorProvider}. * @param socket The corresponding {@link Socket}. */ @@ -57,7 +62,7 @@ protected AFDatagramChannel(AFSelectorProvider selectorProvider, AFDatagramSo /** * Returns the corresponding {@link Socket}. - * + * * @return The socket. */ protected final AFDatagramSocket getAFSocket() { @@ -91,7 +96,7 @@ public final AFDatagramSocket socket() { /** * Returns the binding state of the socket. - * + * * @return true if the socket successfully bound to an address */ public final boolean isBound() { @@ -137,17 +142,53 @@ public final AFDatagramChannel disconnect() throws IOException { @Override public final A receive(ByteBuffer dst) throws IOException { - return afSocket.getAFImpl().receive(dst); + boolean complete = false; + Exception exception = null; + try { + begin(); + A ret = afSocket.getAFImpl().receive(dst); + complete = true; + return ret; + } catch (IOException e) { + throw InterruptibleChannelUtil.ioExceptionOrThrowRuntimeException( // NOPMD.PreserveStackTrace + (exception = InterruptibleChannelUtil.handleException(this, e))); + } finally { + InterruptibleChannelUtil.endInterruptable(this, this::end, complete, exception); + } } @Override public final int send(ByteBuffer src, SocketAddress target) throws IOException { - return afSocket.getAFImpl().send(src, target); + boolean complete = false; + Exception exception = null; + try { + begin(); + int ret = afSocket.getAFImpl().send(src, target); + complete = true; + return ret; + } catch (IOException e) { + throw InterruptibleChannelUtil.ioExceptionOrThrowRuntimeException( // NOPMD.PreserveStackTrace + (exception = InterruptibleChannelUtil.handleException(this, e))); + } finally { + InterruptibleChannelUtil.endInterruptable(this, this::end, complete, exception); + } } @Override public final int read(ByteBuffer dst) throws IOException { - return afSocket.getAFImpl().read(dst, null); + boolean complete = false; + Exception exception = null; + try { + begin(); + int ret = afSocket.getAFImpl().read(dst, null); + complete = true; + return ret; + } catch (IOException e) { + throw InterruptibleChannelUtil.ioExceptionOrThrowRuntimeException( // NOPMD.PreserveStackTrace + (exception = InterruptibleChannelUtil.handleException(this, e))); + } finally { + InterruptibleChannelUtil.endInterruptable(this, this::end, complete, exception); + } } @Override @@ -161,7 +202,19 @@ public final long read(ByteBuffer[] dsts, int offset, int length) throws IOExcep @Override public final int write(ByteBuffer src) throws IOException { - return afSocket.getAFImpl().write(src); + boolean complete = false; + Exception exception = null; + try { + begin(); + int ret = afSocket.getAFImpl().write(src); + complete = true; + return ret; + } catch (IOException e) { + throw InterruptibleChannelUtil.ioExceptionOrThrowRuntimeException( // NOPMD.PreserveStackTrace + (exception = InterruptibleChannelUtil.handleException(this, e))); + } finally { + InterruptibleChannelUtil.endInterruptable(this, this::end, complete, exception); + } } @Override @@ -209,7 +262,7 @@ public final AFDatagramChannel setOption(SocketOption name, T value) if (optionId == null) { throw new UnsupportedOperationException("unsupported option"); } else { - afSocket.getAFImpl().setOption(optionId.intValue(), value); + afSocket.getAFImpl().setOption(optionId, value); } return this; } @@ -224,7 +277,7 @@ public final T getOption(SocketOption name) throws IOException { if (optionId == null) { throw new UnsupportedOperationException("unsupported option"); } else { - return (T) afSocket.getAFImpl().getOption(optionId.intValue()); + return (T) afSocket.getAFImpl().getOption(optionId); } } @@ -244,10 +297,10 @@ public final FileDescriptor getFileDescriptor() throws IOException { /** * Checks if this {@link DatagramSocket}'s bound filename should be removed upon {@link #close()}. - * + * * Deletion is not guaranteed, especially when not supported (e.g., addresses in the abstract * namespace). - * + * * @return {@code true} if an attempt is made to delete the socket file upon {@link #close()}. */ public final boolean isDeleteOnClose() { @@ -256,13 +309,44 @@ public final boolean isDeleteOnClose() { /** * Enables/disables deleting this {@link DatagramSocket}'s bound filename upon {@link #close()}. - * + * * Deletion is not guaranteed, especially when not supported (e.g., addresses in the abstract * namespace). - * + * * @param b Enabled if {@code true}. */ public final void setDeleteOnClose(boolean b) { afSocket.setDeleteOnClose(b); } + + @Override + public void setShutdownOnClose(boolean enabled) { + getAFCore().setShutdownOnClose(enabled); + } + + /** + * Opens a datagram channel. The {@code family} parameter specifies the {@link ProtocolFamily + * protocol family} of the channel's socket. + *

+ * If the {@link ProtocolFamily} is of an {@link AFProtocolFamily}, or {@code UNIX}, the + * corresponding junixsocket implementation is used. In all other cases, the call is delegated to + * {@link DatagramChannel#open()}. + * + * @param family The protocol family. + * @return The new {@link DatagramChannel}. + * @throws IOException on error. + */ + public static DatagramChannel open(ProtocolFamily family) throws IOException { + requireNonNull(family); + + if (family instanceof AFProtocolFamily) { + return ((AFProtocolFamily) family).openDatagramChannel(); + } else if ("UNIX".equals(family.name())) { + return AFUNIXDatagramChannel.open(); + } else if (family instanceof StandardProtocolFamily) { + return DatagramChannel.open(); + } else { + throw new UnsupportedOperationException("Protocol family not supported"); + } + } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFDatagramSocket.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFDatagramSocket.java index d35194d23..c24123fd1 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFDatagramSocket.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFDatagramSocket.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import java.net.SocketImpl; import java.nio.channels.AlreadyBoundException; import java.nio.channels.DatagramChannel; +import java.nio.channels.IllegalBlockingModeException; import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jdt.annotation.Nullable; @@ -36,27 +37,28 @@ /** * A {@link DatagramSocket} implementation that works with junixsocket. - * + * * @param The concrete {@link AFSocketAddress} that is supported by this type. * @author Christian Kohlschütter */ -public abstract class AFDatagramSocket extends DatagramSocket implements - AFSomeSocket, AFSocketExtensions { +public abstract class AFDatagramSocket extends DatagramSocketShim + implements AFSomeSocket, AFSocketExtensions { private static final InetSocketAddress WILDCARD_ADDRESS = new InetSocketAddress(0); private final AFDatagramSocketImpl impl; private final AncillaryDataSupport ancillaryDataSupport; private final AtomicBoolean created = new AtomicBoolean(false); private final AtomicBoolean deleteOnClose = new AtomicBoolean(true); + + @SuppressWarnings("this-escape") private final AFDatagramChannel channel = newChannel(); /** * Creates a new {@link AFDatagramSocket} instance. - * + * * @param impl The corresponding {@link SocketImpl} class. - * @throws IOException on error. */ - protected AFDatagramSocket(final AFDatagramSocketImpl impl) throws IOException { + protected AFDatagramSocket(final AFDatagramSocketImpl impl) { super(impl); this.impl = impl; this.ancillaryDataSupport = impl.ancillaryDataSupport; @@ -64,30 +66,30 @@ protected AFDatagramSocket(final AFDatagramSocketImpl impl) throws IOExceptio /** * Creates a new {@link DatagramChannel} that is associated with this socket. - * + * * @return The channel. */ protected abstract AFDatagramChannel newChannel(); /** - * Returns the {@link AncillaryDataSupport} instance. - * + * Returns the {@code AncillaryDataSupport} instance. + * * @return The instance. */ - protected final AncillaryDataSupport getAncillaryDataSupport() { + final AncillaryDataSupport getAncillaryDataSupport() { return ancillaryDataSupport; } /** * A reference to the constructor of an {@link AFDatagramSocket} subclass. - * + * * @param The concrete {@link AFSocketAddress} that is supported by this type. */ @FunctionalInterface public interface Constructor { /** * Constructs a new {@link DatagramSocket} instance. - * + * * @param fd The file descriptor. * @return The new instance. * @throws IOException on error. @@ -97,7 +99,7 @@ public interface Constructor { /** * Returns the {@link AFSocketAddress} type supported by this socket. - * + * * @return The supported {@link AFSocketAddress}. */ protected final Class socketAddressClass() { @@ -119,7 +121,7 @@ protected static final AFDatagramSocket newInstan /** * Creates a new {@link AFDatagramSocket}. - * + * * @param The concrete {@link AFSocketAddress} that is supported by this type. * @param constructor The supplying constructor. * @param fdObj The file descriptor. @@ -169,12 +171,12 @@ public final void connect(InetAddress address, int port) { /** * Reads the next received packet without actually removing it from the queue. - * + * * In other words, once a packet is received, calling this method multiple times in a row will not * have further effects on the packet contents. - * + * * This call still blocks until at least one packet has been received and added to the queue. - * + * * @param p The packet. * @throws IOException on error. */ @@ -328,10 +330,10 @@ public final synchronized void bind(SocketAddress addr) throws SocketException { /** * Checks if this {@link AFDatagramSocket}'s bound filename should be removed upon * {@link #close()}. - * + * * Deletion is not guaranteed, especially when not supported (e.g., addresses in the abstract * namespace). - * + * * @return {@code true} if an attempt is made to delete the socket file upon {@link #close()}. */ public final boolean isDeleteOnClose() { @@ -340,10 +342,10 @@ public final boolean isDeleteOnClose() { /** * Enables/disables deleting this {@link AFDatagramSocket}'s bound filename upon {@link #close()}. - * + * * Deletion is not guaranteed, especially when not supported (e.g., addresses in the abstract * namespace). - * + * * @param b Enabled if {@code true}. */ public final void setDeleteOnClose(boolean b) { @@ -407,7 +409,7 @@ public final void receive(DatagramPacket p) throws IOException { /** * Returns the address family supported by this implementation. - * + * * @return The family. */ protected final AFAddressFamily addressFamily() { @@ -416,7 +418,7 @@ protected final AFAddressFamily addressFamily() { /** * Returns the internal helper instance for address-specific extensions. - * + * * @return The helper instance. * @throws UnsupportedOperationException if such extensions are not supported for this address * type. @@ -424,4 +426,108 @@ protected final AFAddressFamily addressFamily() { protected AFSocketImplExtensions getImplExtensions() { return getAFImpl(false).getImplExtensions(); } + + /** + * Returns the value of a junixsocket socket option. + * + * @param The type of the socket option value. + * @param name The socket option. + * @return The value of the socket option. + * @throws IOException on error. + */ + @Override + public T getOption(AFSocketOption name) throws IOException { + return getAFImpl().getCore().getOption(name); + } + + /** + * Sets the value of a socket option. + * + * @param The type of the socket option value. + * @param name The socket option. + * @param value The value of the socket option. + * @return this DatagramSocket. + * @throws IOException on error. + */ + @Override + public DatagramSocket setOption(AFSocketOption name, T value) throws IOException { + getAFImpl().getCore().setOption(name, value); + return this; + } + + /** + * Accepts a connection to this socket. Note that 1., the socket must be in {@code listen} state + * by calling {@link #bind(SocketAddress)}, followed by {@link #listen(int)}, and 2., the socket + * type must allow listen/accept. This is true for {@link AFSocketType#SOCK_SEQPACKET} AF_UNIX + * sockets, for example. + * + * @return The accepted datagram socket. + * @throws IOException on error. + * @see #listen(int) + */ + public AFDatagramSocket accept() throws IOException { + return accept1(true); + } + + /** + * Sets this socket into "listen" state, which allows subsequent calls to {@link #accept()} + * receive any connection attempt. Note that 1., the socket must be bound to a local address using + * {@link #bind(SocketAddress)}, and 2., the socket type must allow listen/accept. This is true + * for {@link AFSocketType#SOCK_SEQPACKET} AF_UNIX sockets, for example. + * + * @param backlog The backlog, or {@code 0} for default. + * @throws IOException on error. + */ + public final void listen(int backlog) throws IOException { + FileDescriptor fdesc = getAFImpl().getCore().validFdOrException(); + if (backlog <= 0) { + backlog = 50; + } + NativeUnixSocket.listen(fdesc, backlog); + } + + /** + * Returns a new {@link AFDatagramSocket} instance to be used for {@link #accept()}, i.e., no + * {@link FileDescriptor} is associated. + * + * @return The new instance. + * @throws IOException on error. + */ + protected abstract AFDatagramSocket newDatagramSocketInstance() throws IOException; + + // CPD-OFF + AFDatagramSocket accept1(boolean throwOnFail) throws IOException { + AFDatagramSocket as = newDatagramSocketInstance(); + + boolean success = getAFImpl().accept0(as.getAFImpl(false)); + if (isClosed()) { + // We may have connected to the socket to unblock it + throw new SocketClosedException("Socket is closed"); + } + + if (!success) { + if (throwOnFail) { + if (getChannel().isBlocking()) { + // unexpected + return null; + } else { + // non-blocking socket, nothing to accept + throw new IllegalBlockingModeException(); + } + } else { + return null; + } + } + + as.getAFImpl(true); // trigger create + as.connect(AFSocketAddress.INTERNAL_DUMMY_CONNECT); + as.getAFImpl().updatePorts(getAFImpl().getLocalPort1(), getAFImpl().getRemotePort()); + + return as; + } + + @Override + public void setShutdownOnClose(boolean enabled) { + getAFImpl().getCore().setShutdownOnClose(enabled); + } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFDatagramSocketImpl.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFDatagramSocketImpl.java index b1320085f..dfdb6e789 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFDatagramSocketImpl.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFDatagramSocketImpl.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,8 @@ */ package org.newsclub.net.unix; +import static org.newsclub.net.unix.NativeUnixSocket.SHUT_RD_WR; + import java.io.FileDescriptor; import java.io.IOException; import java.net.DatagramPacket; @@ -28,18 +30,25 @@ import java.net.SocketTimeoutException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; +import java.nio.channels.SelectionKey; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import org.newsclub.net.unix.pool.MutableHolder; +import org.newsclub.net.unix.pool.ObjectPool.Lease; + +import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; /** * A {@link DatagramSocketImpl} implemented by junixsocket. - * + * * @param The associated address type. * @author Christian Kohlschütter */ +@SuppressWarnings("PMD.CyclomaticComplexity") public abstract class AFDatagramSocketImpl extends DatagramSocketImplShim { private final AFSocketType socketType; @@ -49,6 +58,7 @@ public abstract class AFDatagramSocketImpl extends private final AtomicBoolean bound = new AtomicBoolean(false); private final AtomicInteger socketTimeout = new AtomicInteger(0); + private int localPort; private int remotePort = 0; private final AFAddressFamily<@NonNull A> addressFamily; private AFSocketImplExtensions implExtensions = null; @@ -56,14 +66,14 @@ public abstract class AFDatagramSocketImpl extends /** * Constructs a new {@link AFDatagramSocketImpl} using the given {@link FileDescriptor} (or null * to create a new one). - * + * * @param addressFamily The address family. * @param fd The file descriptor, or {@code null}. * @param socketType The socket type. - * @throws IOException on error. */ + @SuppressWarnings("this-escape") protected AFDatagramSocketImpl(AFAddressFamily<@NonNull A> addressFamily, FileDescriptor fd, - AFSocketType socketType) throws IOException { + AFSocketType socketType) { super(); this.addressFamily = addressFamily; // FIXME verify fd @@ -102,8 +112,10 @@ final void connect(AFSocketAddress socketAddress) throws IOException { if (socketAddress == AFSocketAddress.INTERNAL_DUMMY_CONNECT) { // NOPMD return; } - ByteBuffer ab = socketAddress.getNativeAddressDirectBuffer(); - NativeUnixSocket.connect(ab, ab.limit(), fd, -1); + try (Lease abLease = socketAddress.getNativeAddressDirectBuffer()) { + ByteBuffer ab = abLease.get(); + NativeUnixSocket.connect(ab, ab.limit(), fd, -1); + } this.remotePort = socketAddress.getPort(); } @@ -114,7 +126,7 @@ protected final void disconnect() { connected.set(false); this.remotePort = 0; } catch (IOException e) { - e.printStackTrace(); + StackTraceUtil.printStackTrace(e); } } @@ -140,13 +152,9 @@ final void bind(AFSocketAddress socketAddress) throws SocketException { if (socketAddress == AFSocketAddress.INTERNAL_DUMMY_BIND) { // NOPMD return; } - try { - ByteBuffer ab; - if (socketAddress == null) { - ab = AFSocketAddress.getNativeAddressDirectBuffer(0); - } else { - ab = socketAddress.getNativeAddressDirectBuffer(); - } + try (Lease abLease = socketAddress == null ? AFSocketAddress + .getNativeAddressDirectBuffer(0) : socketAddress.getNativeAddressDirectBuffer()) { + ByteBuffer ab = abLease.get(); NativeUnixSocket.bind(ab, ab.limit(), fd, NativeUnixSocket.OPT_DGRAM_MODE); if (socketAddress == null) { this.localPort = 0; @@ -166,62 +174,166 @@ protected final void receive(DatagramPacket p) throws IOException { recv(p, 0); } + @SuppressWarnings({ + "PMD.NcssCount", "PMD.CognitiveComplexity", "PMD.CyclomaticComplexity", + "PMD.NPathComplexity"}) + @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") private void recv(DatagramPacket p, int options) throws IOException { int len = p.getLength(); FileDescriptor fdesc = core.validFdOrException(); - ByteBuffer datagramPacketBuffer = core.getThreadLocalDirectByteBuffer(len); - len = Math.min(len, datagramPacketBuffer.capacity()); - - options |= core.isBlocking() ? 0 : NativeUnixSocket.OPT_NON_BLOCKING; - - ByteBuffer socketAddressBuffer = AFSocketAddress.SOCKETADDRESS_BUFFER_TL.get(); - int count = NativeUnixSocket.receive(fdesc, datagramPacketBuffer, 0, len, socketAddressBuffer, - options, ancillaryDataSupport, socketTimeout.get()); - if (count > len) { - throw new IllegalStateException("count > len: " + count + " > " + len); - } else if (count == -1) { - throw new SocketTimeoutException(); - } else if (count < 0) { - throw new IllegalStateException("count: " + count + " < 0"); + final boolean virtualBlocking = (ThreadUtil.isVirtualThread() && core.isBlocking()) || core + .isVirtualBlocking(); + final long now; + if (virtualBlocking) { + now = System.currentTimeMillis(); + } else { + now = 0; + } + if (virtualBlocking || !core.isBlocking()) { + options |= NativeUnixSocket.OPT_NON_BLOCKING; } - datagramPacketBuffer.limit(count); - datagramPacketBuffer.rewind(); - datagramPacketBuffer.get(p.getData(), p.getOffset(), count); - p.setLength(count); + boolean park = false; + virtualThreadLoop : do { + if (virtualBlocking) { + if (park) { + VirtualThreadPoller.INSTANCE.parkThreadUntilReady(fdesc, SelectionKey.OP_WRITE, now, + socketTimeout::get, this::close); + } + core.configureVirtualBlocking(true); + } + + try (Lease> lease = core.getPrivateDirectByteBuffer(len); + Lease socketAddressBufferLease = AFSocketAddress.SOCKETADDRESS_BUFFER_TL + .take()) { + ByteBuffer datagramPacketBuffer = Objects.requireNonNull(lease.get().get()); + len = Math.min(len, datagramPacketBuffer.capacity()); + + ByteBuffer socketAddressBuffer = socketAddressBufferLease.get(); + int count = NativeUnixSocket.receive(fdesc, datagramPacketBuffer, 0, len, + socketAddressBuffer, options, ancillaryDataSupport, socketTimeout.get()); + if (count == 0 && virtualBlocking) { + // try again + park = true; + continue virtualThreadLoop; + } - A addr = AFSocketAddress.ofInternal(socketAddressBuffer, getAddressFamily()); - p.setAddress(addr == null ? null : addr.getInetAddress()); - p.setPort(remotePort); + if (count > len) { + throw new IllegalStateException("count > len: " + count + " > " + len); + } else if (count == -1) { + throw new SocketTimeoutException(); + } else if (count < 0) { + throw new IllegalStateException("count: " + count + " < 0"); + } + datagramPacketBuffer.limit(count); + datagramPacketBuffer.rewind(); + datagramPacketBuffer.get(p.getData(), p.getOffset(), count); + + p.setLength(count); + + A addr = AFSocketAddress.ofInternal(socketAddressBuffer, getAddressFamily()); + p.setAddress(addr == null ? null : addr.getInetAddress()); + p.setPort(remotePort); + } catch (SocketTimeoutException e) { // NOPMD.ExceptionAsFlowControl + if (virtualBlocking) { + // try again + park = true; + continue virtualThreadLoop; + } else { + throw e; + } + } finally { + if (virtualBlocking) { + core.configureVirtualBlocking(false); + } + } + break; // NOPMD.AvoidBranchingStatementAsLastInLoop virtualThreadLoop + } while (true); // NOPMD.WhileLoopWithLiteralBoolean } + @SuppressWarnings({ + "PMD.NcssCount", "PMD.CognitiveComplexity", "PMD.CyclomaticComplexity", + "PMD.NPathComplexity"}) + @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") @Override protected final void send(DatagramPacket p) throws IOException { InetAddress addr = p.getAddress(); ByteBuffer sendToBuf = null; int sendToBufLen = 0; + + byte[] addrBytes; if (addr != null) { - byte[] addrBytes = AFInetAddress.unwrapAddress(addr, getAddressFamily()); - if (addrBytes != null) { - sendToBuf = AFSocketAddress.SOCKETADDRESS_BUFFER_TL.get(); + addrBytes = AFInetAddress.unwrapAddress(addr, getAddressFamily()); + } else { + addrBytes = null; + } + + try (Lease sendToBufLease = addrBytes == null ? null + : AFSocketAddress.SOCKETADDRESS_BUFFER_TL.take()) { + if (sendToBufLease != null) { + sendToBuf = sendToBufLease.get(); sendToBufLen = NativeUnixSocket.bytesToSockAddr(getAddressFamily().getDomain(), sendToBuf, addrBytes); + sendToBuf.position(0); if (sendToBufLen == -1) { throw new SocketException("Unsupported domain"); } } } FileDescriptor fdesc = core.validFdOrException(); - int len = p.getLength(); - ByteBuffer datagramPacketBuffer = core.getThreadLocalDirectByteBuffer(len); - datagramPacketBuffer.clear(); - datagramPacketBuffer.put(p.getData(), p.getOffset(), p.getLength()); - NativeUnixSocket.send(fdesc, datagramPacketBuffer, 0, len, sendToBuf, sendToBufLen, - /* NativeUnixSocket.OPT_NON_BLOCKING | */ - NativeUnixSocket.OPT_DGRAM_MODE, ancillaryDataSupport); + final boolean virtualBlocking = (ThreadUtil.isVirtualThread() && core.isBlocking()) || core + .isVirtualBlocking(); + final long now; + final int opt; + if (virtualBlocking) { + now = System.currentTimeMillis(); + opt = NativeUnixSocket.OPT_DGRAM_MODE | NativeUnixSocket.OPT_NON_BLOCKING; + } else { + now = 0; + opt = NativeUnixSocket.OPT_DGRAM_MODE; + } + + boolean park = false; + virtualThreadLoop : do { + if (virtualBlocking) { + if (park) { + VirtualThreadPoller.INSTANCE.parkThreadUntilReady(fdesc, SelectionKey.OP_WRITE, now, + socketTimeout::get, this::close); + } + core.configureVirtualBlocking(true); + } + + try (Lease> lease = core.getPrivateDirectByteBuffer(len)) { + ByteBuffer datagramPacketBuffer = Objects.requireNonNull(lease.get().get()); + datagramPacketBuffer.clear(); + datagramPacketBuffer.put(p.getData(), p.getOffset(), p.getLength()); + datagramPacketBuffer.flip(); + + int written = NativeUnixSocket.send(fdesc, datagramPacketBuffer, 0, len, sendToBuf, + sendToBufLen, opt, ancillaryDataSupport); + if (written == 0 && virtualBlocking) { + // try again + park = true; + continue virtualThreadLoop; + } + } catch (SocketTimeoutException e) { + if (virtualBlocking) { + // try again + park = true; + continue virtualThreadLoop; + } else { + throw e; + } + } finally { + if (virtualBlocking) { + core.configureVirtualBlocking(false); + } + } + break; // NOPMD.AvoidBranchingStatementAsLastInLoop virtualThreadLoop + } while (true); // NOPMD.WhileLoopWithLiteralBoolean } @Override @@ -236,11 +348,13 @@ protected final int peekData(DatagramPacket p) throws IOException { } @Override + @Deprecated protected final byte getTTL() throws IOException { return (byte) (getTimeToLive() & 0xFF); } @Override + @Deprecated protected final void setTTL(byte ttl) throws IOException { // ignored } @@ -300,7 +414,7 @@ public void setOption(int optID, Object value) throws SocketException { @SuppressWarnings("unchecked") final A receive(ByteBuffer dst) throws IOException { try { - return (A) core.receive(dst); + return (A) core.receive(dst, socketTimeout::get); } catch (SocketClosedException e) { throw (ClosedChannelException) new ClosedChannelException().initCause(e); } @@ -308,7 +422,7 @@ final A receive(ByteBuffer dst) throws IOException { final int send(ByteBuffer src, SocketAddress target) throws IOException { try { - return core.write(src, target, 0); + return core.write(src, socketTimeout::get, target, 0); } catch (SocketClosedException e) { throw (ClosedChannelException) new ClosedChannelException().initCause(e); } @@ -316,7 +430,7 @@ final int send(ByteBuffer src, SocketAddress target) throws IOException { final int read(ByteBuffer dst, ByteBuffer socketAddressBuffer) throws IOException { try { - return core.read(dst, socketAddressBuffer, 0); + return core.read(dst, socketTimeout::get, socketAddressBuffer, 0); } catch (SocketClosedException e) { throw (ClosedChannelException) new ClosedChannelException().initCause(e); } @@ -324,7 +438,7 @@ final int read(ByteBuffer dst, ByteBuffer socketAddressBuffer) throws IOExceptio final int write(ByteBuffer src) throws IOException { try { - return core.write(src); + return core.write(src, socketTimeout::get); } catch (SocketClosedException e) { throw (ClosedChannelException) new ClosedChannelException().initCause(e); } @@ -375,7 +489,7 @@ final void updatePorts(int local, int remote) { /** * Returns the address family supported by this implementation. - * + * * @return The family. */ protected final AFAddressFamily<@NonNull A> getAddressFamily() { @@ -384,7 +498,7 @@ final void updatePorts(int local, int remote) { /** * Returns the internal helper instance for address-specific extensions. - * + * * @return The helper instance. * @throws UnsupportedOperationException if such extensions are not supported for this address * type. @@ -395,4 +509,90 @@ protected final synchronized AFSocketImplExtensions getImplExtensions() { } return implExtensions; } + + // CPD-OFF + @SuppressWarnings("Finally" /* errorprone */) + final boolean accept0(AFDatagramSocketImpl socket) throws IOException { + FileDescriptor fdesc = core.validFdOrException(); + if (isClosed()) { + throw new SocketException("Socket is closed"); + } else if (!isBound()) { + throw new SocketException("Socket is not bound"); + } + + AFSocketAddress socketAddress = core.socketAddress; + AFSocketAddress boundSocketAddress = getLocalSocketAddress(); + if (boundSocketAddress != null) { + // Always resolve bound address from wildcard address, etc. + core.socketAddress = socketAddress = boundSocketAddress; + } + + if (socketAddress == null) { + throw new SocketException("Socket is not bound"); + } + + final AFDatagramSocketImpl si = socket; + core.incPendingAccepts(); + try (Lease abLease = socketAddress.getNativeAddressDirectBuffer()) { + ByteBuffer ab = abLease.get(); + + SocketException caught = null; + try { + if (!NativeUnixSocket.accept(ab, ab.limit(), fdesc, si.fd, core.inode.get(), socketTimeout + .get())) { + return false; + } + } catch (SocketException e) { // NOPMD.ExceptionAsFlowControl + caught = e; + } finally { // NOPMD.DoNotThrowExceptionInFinally + if (!isBound() || isClosed()) { + if (getCore().isShutdownOnClose()) { + try { + NativeUnixSocket.shutdown(si.fd, SHUT_RD_WR); + } catch (Exception e) { + // ignore + } + } + try { + NativeUnixSocket.close(si.fd); + } catch (Exception e) { + // ignore + } + if (caught != null) { + throw caught; + } else { + throw new SocketClosedException("Socket is closed"); + } + } else if (caught != null) { + throw caught; + } + } + } finally { + core.decPendingAccepts(); + } + si.setSocketAddress(socketAddress); + si.connected.set(true); + + return true; + } + + final int getLocalPort1() { + return localPort; + } + + final int getRemotePort() { + return remotePort; + } + + final void setSocketAddress(AFSocketAddress socketAddress) { + if (socketAddress == null) { + this.core.socketAddress = null; + this.localPort = -1; + } else { + this.core.socketAddress = socketAddress; + if (this.localPort <= 0) { + this.localPort = socketAddress.getPort(); + } + } + } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFFunction.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFFunction.java new file mode 100644 index 000000000..03614c08f --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFFunction.java @@ -0,0 +1,37 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +/** + * junixsocket-internal variant of {@code java.util.Function}, to allow compiling junixsocket-common + * with retrolambda for Java 1.7. + * + * @param the type of the input to the function + * @param the type of the result of the function + */ +@FunctionalInterface +interface AFFunction { + + /** + * Applies this function to the given argument. + * + * @param t the function argument + * @return the function result + */ + R apply(T t); +} \ No newline at end of file diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFFuture.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFFuture.java new file mode 100644 index 000000000..d527900f7 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFFuture.java @@ -0,0 +1,48 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import org.eclipse.jdt.annotation.Nullable; + +/** + * junixsocket-internal variant of a bare-bones {@link Future}, to allow compiling + * junixsocket-common with retrolambda for Java 1.7. + * + * @param the type of results supplied by this supplier + */ +@FunctionalInterface +interface AFFuture { + + /** + * Waits if necessary for the computation to complete, and then retrieves its result. + * + * @return the computed result + * @throws CancellationException if the computation was cancelled + * @throws ExecutionException if the computation threw an exception + * @throws InterruptedException if the current thread was interrupted while waiting + */ + T get() throws InterruptedException, ExecutionException; + + static <@Nullable U> AFFuture supplyAsync(AFSupplier supplier) { + return Java7Util.supplyAsync(supplier); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericDatagramChannel.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericDatagramChannel.java new file mode 100644 index 000000000..71f483965 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericDatagramChannel.java @@ -0,0 +1,32 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.nio.channels.DatagramChannel; + +/** + * A {@link DatagramChannel} implementation that works with {@code AF_SYSTEM} sockets. + * + * @author Christian Kohlschütter + */ +final class AFGenericDatagramChannel extends AFDatagramChannel implements + AFGenericSocketExtensions { + AFGenericDatagramChannel(AFGenericDatagramSocket socket) { + super(AFGenericSelectorProvider.getInstance(), socket); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericDatagramSocket.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericDatagramSocket.java new file mode 100644 index 000000000..f8f2ccb44 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericDatagramSocket.java @@ -0,0 +1,84 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.DatagramSocket; + +/** + * A {@link DatagramSocket} implementation that works with {@code AF_SYSTEM} sockets. + * + * @author Christian Kohlschütter + */ +final class AFGenericDatagramSocket extends AFDatagramSocket implements + AFGenericSocketExtensions { + + AFGenericDatagramSocket(FileDescriptor fd) throws IOException { + this(fd, AFSocketType.SOCK_DGRAM); + } + + AFGenericDatagramSocket(FileDescriptor fd, AFSocketType socketType) throws IOException { + super(new AFGenericDatagramSocketImpl(fd, socketType)); + } + + @Override + protected AFGenericDatagramChannel newChannel() { + return new AFGenericDatagramChannel(this); + } + + /** + * Returns a new {@link AFGenericDatagramSocket} instance, using the default + * {@link AFSocketType#SOCK_DGRAM} socket type. + * + * @return The new instance. + * @throws IOException on error. + */ + public static AFGenericDatagramSocket newInstance() throws IOException { + return (AFGenericDatagramSocket) newInstance(AFGenericDatagramSocket::new); + } + + /** + * Returns a new {@link AFGenericDatagramSocket} instance for the given socket type. + * + * @param socketType The socket type. + * @return The new instance. + * @throws IOException on error. + */ + public static AFGenericDatagramSocket newInstance(AFSocketType socketType) throws IOException { + return (AFGenericDatagramSocket) newInstance((fd) -> { + return new AFGenericDatagramSocket(fd, socketType); + }); + } + + @Override + public AFGenericDatagramChannel getChannel() { + return (AFGenericDatagramChannel) super.getChannel(); + } + + @Override + protected AFGenericSocketImplExtensions getImplExtensions() { + return (AFGenericSocketImplExtensions) super.getImplExtensions(); + } + + @Override + protected AFDatagramSocket newDatagramSocketInstance() + throws IOException { + return new AFGenericDatagramSocket(null); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericDatagramSocketImpl.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericDatagramSocketImpl.java new file mode 100644 index 000000000..705182f42 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericDatagramSocketImpl.java @@ -0,0 +1,27 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.FileDescriptor; +import java.io.IOException; + +final class AFGenericDatagramSocketImpl extends AFDatagramSocketImpl { + AFGenericDatagramSocketImpl(FileDescriptor fd, AFSocketType socketType) throws IOException { + super(AFGenericSelectorProvider.AF_GENERIC, fd, socketType); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericProtocolFamily.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericProtocolFamily.java new file mode 100644 index 000000000..f8f3eeb5d --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericProtocolFamily.java @@ -0,0 +1,32 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.net.ProtocolFamily; + +/** + * Describes the protocol families supported by junixsocket-afsystem. + * + * @author Christian Kohlschütter + */ +enum AFGenericProtocolFamily implements ProtocolFamily { + /** + * generic sockets. + */ + GENERIC; +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSelectorProvider.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSelectorProvider.java new file mode 100644 index 000000000..8e9634c6b --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSelectorProvider.java @@ -0,0 +1,189 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.IOException; +import java.net.ProtocolFamily; +import java.net.SocketAddress; + +import org.eclipse.jdt.annotation.NonNull; + +import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; + +/** + * Service-provider class for junixsocket selectors and selectable channels. + */ +final class AFGenericSelectorProvider extends AFSelectorProvider { + private static final AFGenericSelectorProvider INSTANCE = new AFGenericSelectorProvider(); + + @SuppressWarnings("null") + static final AFAddressFamily<@NonNull AFGenericSocketAddress> AF_GENERIC = AFAddressFamily + .registerAddressFamilyImpl("generic", AFGenericSocketAddress.addressFamily(), + new AFAddressFamilyConfig() { + + @Override + protected Class> socketClass() { + return AFGenericSocket.class; + } + + @Override + protected AFSocket.Constructor socketConstructor() { + return AFGenericSocket::new; + } + + @Override + protected Class> serverSocketClass() { + return AFGenericServerSocket.class; + } + + @Override + protected AFServerSocket.Constructor serverSocketConstructor() { + return AFGenericServerSocket::new; + } + + @Override + protected Class> socketChannelClass() { + return AFGenericSocketChannel.class; + } + + @Override + protected Class> serverSocketChannelClass() { + return AFGenericServerSocketChannel.class; + } + + @Override + protected Class> datagramSocketClass() { + return AFGenericDatagramSocket.class; + } + + @Override + protected AFDatagramSocket.Constructor datagramSocketConstructor() { + return AFGenericDatagramSocket::new; + } + + @Override + protected Class> datagramChannelClass() { + return AFGenericDatagramChannel.class; + } + }); + + private AFGenericSelectorProvider() { + super(); + } + + /** + * Returns the singleton instance. + * + * @return The instance. + */ + @SuppressFBWarnings("MS_EXPOSE_REP") + public static AFGenericSelectorProvider getInstance() { + return INSTANCE; + } + + /** + * Returns the singleton instance. + * + * @return The instance. + */ + public static AFGenericSelectorProvider provider() { + return getInstance(); + } + + /** + * Constructs a new socket pair from two sockets. + * + * @param s1 Some socket, the first one. + * @param s2 Some socket, the second one. + * @return The pair. + */ + @Override + protected

AFSocketPair

newSocketPair(P s1, P s2) { + return new AFGenericSocketPair<>(s1, s2); + } + + @SuppressWarnings("unchecked") + @Override + public AFGenericSocketPair openSocketChannelPair() throws IOException { + return (AFGenericSocketPair) super.openSocketChannelPair(); + } + + @SuppressWarnings("unchecked") + @Override + public AFGenericSocketPair openDatagramChannelPair() + throws IOException { + return (AFGenericSocketPair) super.openDatagramChannelPair(); + } + + @SuppressWarnings("unchecked") + @Override + public AFGenericSocketPair openDatagramChannelPair(AFSocketType type) + throws IOException { + return (AFGenericSocketPair) super.openDatagramChannelPair(type); + } + + @Override + protected AFGenericSocket newSocket() throws IOException { + return AFGenericSocket.newInstance(); + } + + @Override + public AFGenericDatagramChannel openDatagramChannel() throws IOException { + return AFGenericDatagramSocket.newInstance().getChannel(); + } + + @Override + public AFGenericDatagramChannel openDatagramChannel(AFSocketType type) throws IOException { + return AFGenericDatagramSocket.newInstance(type).getChannel(); + } + + @Override + public AFGenericDatagramChannel openDatagramChannel(ProtocolFamily family) throws IOException { + return (AFGenericDatagramChannel) super.openDatagramChannel(family); + } + + @Override + public AFGenericServerSocketChannel openServerSocketChannel() throws IOException { + return AFGenericServerSocket.newInstance().getChannel(); + } + + @Override + public AFGenericServerSocketChannel openServerSocketChannel(SocketAddress sa) throws IOException { + return AFGenericServerSocket.bindOn(AFGenericSocketAddress.unwrap(sa)).getChannel(); + } + + @Override + public AFGenericSocketChannel openSocketChannel() throws IOException { + return (AFGenericSocketChannel) super.openSocketChannel(); + } + + @Override + public AFGenericSocketChannel openSocketChannel(SocketAddress sa) throws IOException { + return AFGenericSocket.connectTo(AFGenericSocketAddress.unwrap(sa)).getChannel(); + } + + @Override + protected ProtocolFamily protocolFamily() { + return AFGenericProtocolFamily.GENERIC; + } + + @Override + protected AFAddressFamily<@NonNull AFGenericSocketAddress> addressFamily() { + return AF_GENERIC; + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericServerSocket.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericServerSocket.java new file mode 100644 index 000000000..45f8a25f8 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericServerSocket.java @@ -0,0 +1,118 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.SocketException; + +/** + * The server part of a "unknown-type" socket. + * + * @author Christian Kohlschütter + */ +final class AFGenericServerSocket extends AFServerSocket { + AFGenericServerSocket(FileDescriptor fdObj) throws IOException { + super(fdObj); + } + + @Override + protected AFServerSocketChannel newChannel() { + return new AFGenericServerSocketChannel(this); + } + + @Override + public AFGenericServerSocketChannel getChannel() { + return (AFGenericServerSocketChannel) super.getChannel(); + } + + /** + * Returns a new, unbound unkown-type {@link ServerSocket}. + * + * @return The new, unbound {@link AFServerSocket}. + * @throws IOException if the operation fails. + */ + public static AFGenericServerSocket newInstance() throws IOException { + return (AFGenericServerSocket) AFServerSocket.newInstance(AFGenericServerSocket::new); + } + + static AFGenericServerSocket newInstance(FileDescriptor fdObj, int localPort, int remotePort) + throws IOException { + return (AFGenericServerSocket) AFServerSocket.newInstance(AFGenericServerSocket::new, fdObj, + localPort, remotePort); + } + + /** + * Returns a new unkown-type {@link ServerSocket} that is bound to the given + * {@link AFGenericSocketAddress}. + * + * @param addr The socket file to bind to. + * @return The new, bound {@link AFServerSocket}. + * @throws IOException if the operation fails. + */ + public static AFGenericServerSocket bindOn(final AFGenericSocketAddress addr) throws IOException { + return (AFGenericServerSocket) AFServerSocket.bindOn(AFGenericServerSocket::new, addr); + } + + /** + * Returns a new unkown-type {@link ServerSocket} that is bound to the given + * {@link AFSocketAddress}. + * + * @param addr The socket file to bind to. + * @param deleteOnClose If {@code true}, the socket file (if the address points to a file) will be + * deleted upon {@link #close}. + * @return The new, bound {@link AFServerSocket}. + * @throws IOException if the operation fails. + */ + public static AFGenericServerSocket bindOn(final AFGenericSocketAddress addr, + boolean deleteOnClose) throws IOException { + return (AFGenericServerSocket) AFServerSocket.bindOn(AFGenericServerSocket::new, addr, + deleteOnClose); + } + + /** + * Returns a new, unbound unkown-type {@link ServerSocket} that will always bind to the + * given address, regardless of any socket address used in a call to bind. + * + * @param forceAddr The address to use. + * @return The new, yet unbound {@link AFServerSocket}. + * @throws IOException if an exception occurs. + */ + public static AFGenericServerSocket forceBindOn(final AFGenericSocketAddress forceAddr) + throws IOException { + return (AFGenericServerSocket) AFServerSocket.forceBindOn(AFGenericServerSocket::new, + forceAddr); + } + + @Override + protected AFSocketImpl newImpl(FileDescriptor fdObj) + throws SocketException { + return new AFGenericSocketImpl(fdObj); + } + + @Override + protected AFSocket newSocketInstance() throws IOException { + return AFGenericSocket.newInstance(); + } + + @Override + public AFGenericSocket accept() throws IOException { + return (AFGenericSocket) super.accept(); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericServerSocketChannel.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericServerSocketChannel.java new file mode 100644 index 000000000..85edc918c --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericServerSocketChannel.java @@ -0,0 +1,46 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.IOException; + +/** + * A selectable channel for stream-oriented listening sockets. + * + * @author Christian Kohlschütter + */ +final class AFGenericServerSocketChannel extends AFServerSocketChannel { + AFGenericServerSocketChannel(AFGenericServerSocket socket) { + super(socket, AFGenericSelectorProvider.getInstance()); + } + + /** + * Opens a server-socket channel. + * + * @return The new channel + * @throws IOException on error. + */ + public static AFGenericServerSocketChannel open() throws IOException { + return AFGenericServerSocket.newInstance().getChannel(); + } + + @Override + public AFGenericSocketChannel accept() throws IOException { + return (AFGenericSocketChannel) super.accept(); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocket.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocket.java new file mode 100644 index 000000000..c8c5f3267 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocket.java @@ -0,0 +1,119 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.Socket; +import java.net.SocketException; + +/** + * Implementation of an unknown-type socket. + * + * @author Christian Kohlschütter + */ +final class AFGenericSocket extends AFSocket implements + AFGenericSocketExtensions { + private static AFGenericSocketImplExtensions staticExtensions = null; + + AFGenericSocket(FileDescriptor fdObj, AFSocketFactory factory) + throws SocketException { + super(new AFGenericSocketImpl(fdObj), factory); + } + + @SuppressWarnings("unused") + private static synchronized AFGenericSocketImplExtensions getStaticImplExtensions() + throws IOException { + if (staticExtensions == null) { + try (AFGenericSocket socket = new AFGenericSocket(null, null)) { + staticExtensions = (AFGenericSocketImplExtensions) socket.getImplExtensions(); + } + } + return staticExtensions; + } + + /** + * Returns true iff {@link AFGenericSocket}s (sockets of unknown/otherwise + * unsupported type) are supported by the current Java VM and the kernel. + * + * To support {@link AFGenericSocket}s, a custom JNI library must be loaded that is supplied with + * junixsocket. + * + * This call is equivalent to checking {@link AFSocket#isSupported()}. + * + * @return {@code true} iff supported. + */ + public static boolean isSupported() { + return AFSocket.isSupported(); + } + + @Override + protected AFGenericSocketChannel newChannel() { + return new AFGenericSocketChannel(this); + } + + /** + * Creates a new, unbound {@link AFSocket}. + * + * This "default" implementation is a bit "lenient" with respect to the specification. + * + * In particular, we ignore calls to {@link Socket#getTcpNoDelay()} and + * {@link Socket#setTcpNoDelay(boolean)}. + * + * @return A new, unbound socket. + * @throws IOException if the operation fails. + */ + public static AFGenericSocket newInstance() throws IOException { + return (AFGenericSocket) AFSocket.newInstance(AFGenericSocket::new, + (AFGenericSocketFactory) null); + } + + static AFGenericSocket newInstance(AFGenericSocketFactory factory) throws SocketException { + return (AFGenericSocket) AFSocket.newInstance(AFGenericSocket::new, factory); + } + + /** + * Creates a new, unbound, "strict" {@link AFSocket}. + * + * This call uses an implementation that tries to be closer to the specification than + * {@link #newInstance()}, at least for some cases. + * + * @return A new, unbound socket. + * @throws IOException if the operation fails. + */ + public static AFGenericSocket newStrictInstance() throws IOException { + return (AFGenericSocket) AFSocket.newInstance(AFGenericSocket::new, + (AFGenericSocketFactory) null); + } + + /** + * Creates a new {@link AFSocket} and connects it to the given {@link AFGenericSocketAddress}. + * + * @param addr The address to connect to. + * @return A new, connected socket. + * @throws IOException if the operation fails. + */ + public static AFGenericSocket connectTo(AFGenericSocketAddress addr) throws IOException { + return (AFGenericSocket) AFSocket.connectTo(AFGenericSocket::new, addr); + } + + @Override + public AFGenericSocketChannel getChannel() { + return (AFGenericSocketChannel) super.getChannel(); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketAddress.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketAddress.java new file mode 100644 index 000000000..93c3f41bb --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketAddress.java @@ -0,0 +1,234 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.InetAddress; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +import org.newsclub.net.unix.pool.ObjectPool.Lease; + +/** + * An {@link AFSocketAddress} for unknown socket types. + * + * @author Christian Kohlschütter + */ +public final class AFGenericSocketAddress extends AFSocketAddress { + private static final long serialVersionUID = 1L; // do not change! + + private static AFAddressFamily family; + private static final String SELECTOR_PROVIDER_CLASS = + "org.newsclub.net.unix.generic.AFGenericSelectorProvider"; + + private AFGenericSocketAddress(int port, final byte[] socketAddress, + Lease nativeAddress) throws SocketException { + super(port, socketAddress, nativeAddress, addressFamily()); + } + + private static AFGenericSocketAddress newAFSocketAddress(int port, final byte[] socketAddress, + Lease nativeAddress) throws SocketException { + return newDeserializedAFSocketAddress(port, socketAddress, nativeAddress, addressFamily(), + AFGenericSocketAddress::new); + } + + /** + * Returns an {@link AFGenericSocketAddress} given a special {@link InetAddress} that encodes the + * byte sequence of an unknown-type socket address, like those returned by {@link #wrapAddress()}. + * + * @param address The "special" {@link InetAddress}. + * @param port The port (use 0 for "none"). + * @return The {@link AFGenericSocketAddress} instance. + * @throws SocketException if the operation fails, for example when an unsupported address is + * specified. + */ + public static AFGenericSocketAddress unwrap(InetAddress address, int port) + throws SocketException { + return AFSocketAddress.unwrap(address, port, addressFamily()); + } + + /** + * Returns an {@link AFGenericSocketAddress} given a special {@link InetAddress} hostname that + * encodes the byte sequence of an unknown-type socket address, like those returned by + * {@link #wrapAddress()}. + * + * @param hostname The "special" hostname, as provided by {@link InetAddress#getHostName()}. + * @param port The port (use 0 for "none"). + * @return The {@link AFGenericSocketAddress} instance. + * @throws SocketException if the operation fails, for example when an unsupported address is + * specified. + */ + public static AFGenericSocketAddress unwrap(String hostname, int port) throws SocketException { + return AFSocketAddress.unwrap(hostname, port, addressFamily()); + } + + /** + * Returns an {@link AFGenericSocketAddress} given a generic {@link SocketAddress}. + * + * @param address The address to unwrap. + * @return The {@link AFGenericSocketAddress} instance. + * @throws SocketException if the operation fails, for example when an unsupported address is + * specified. + */ + public static AFGenericSocketAddress unwrap(SocketAddress address) throws SocketException { + Objects.requireNonNull(address); + if (!isSupportedAddress(address)) { + throw new SocketException("Unsupported address"); + } + return (AFGenericSocketAddress) address; + } + + @Override + public String toString() { + int port = getPort(); + + return getClass().getName() + "[" + (port == 0 ? "" : "port=" + port + ";") + "bytes=" + Arrays + .toString(getBytes()) + "]"; + } + + /** + * Returns the native representation of this generic address, which is most likely not portable. + *

+ * The address contains the sa_family identifier as the first byte, and, on some platforms only, + * the address length, as the second byte. + * + * @return A new byte array containing the system-specific representation of that address. + */ + public byte[] toBytes() { + byte[] bytes = getBytes(); + return Arrays.copyOf(bytes, bytes.length); + } + + @Override + public boolean hasFilename() { + return false; + } + + @Override + public File getFile() throws FileNotFoundException { + throw new FileNotFoundException("no file"); + } + + /** + * Checks if an {@link InetAddress} can be unwrapped to an {@link AFGenericSocketAddress}. + * + * @param addr The instance to check. + * @return {@code true} if so. + * @see #wrapAddress() + * @see #unwrap(InetAddress, int) + */ + public static boolean isSupportedAddress(InetAddress addr) { + return AFSocketAddress.isSupportedAddress(addr, addressFamily()); + } + + /** + * Checks if a {@link SocketAddress} can be unwrapped to an {@link AFGenericSocketAddress}. + * + * @param addr The instance to check. + * @return {@code true} if so. + * @see #unwrap(InetAddress, int) + */ + public static boolean isSupportedAddress(SocketAddress addr) { + return (addr instanceof AFGenericSocketAddress); + } + + /** + * Returns the corresponding {@link AFAddressFamily}. + * + * @return The address family instance. + */ + @SuppressWarnings("null") + public static synchronized AFAddressFamily addressFamily() { + if (family == null) { + family = AFAddressFamily.registerAddressFamily("generic", // + AFGenericSocketAddress.class, new AFSocketAddressConfig() { + + private final AFSocketAddressConstructor addrConstr = + isUseDeserializationForInit() ? AFGenericSocketAddress::newAFSocketAddress + : AFGenericSocketAddress::new; + + @Override + protected AFGenericSocketAddress parseURI(URI u, int port) throws SocketException { + return AFGenericSocketAddress.of(u, port); + } + + @Override + protected AFSocketAddressConstructor addressConstructor() { + return addrConstr; + } + + @Override + protected String selectorProviderClassname() { + return SELECTOR_PROVIDER_CLASS; + } + + @Override + protected Set uriSchemes() { + return Collections.emptySet(); + } + }); + try { + Class.forName(SELECTOR_PROVIDER_CLASS); + } catch (ClassNotFoundException e) { + // ignore + } + } + return family; + } + + /** + * Returns an {@link AFGenericSocketAddress} for the given URI, if possible. + * + * @param uri The URI. + * @return The address. + * @throws SocketException if the operation fails. + */ + @SuppressWarnings("PMD.ShortMethodName") + public static AFGenericSocketAddress of(URI uri) throws SocketException { + return of(uri, -1); + } + + /** + * Returns an {@link AFGenericSocketAddress} for the given URI, if possible. + * + * @param uri The URI. + * @param overridePort The port to forcibly use, or {@code -1} for "don't override". + * @return The address. + * @throws SocketException if the operation fails. + */ + @SuppressWarnings({ + "PMD.CognitiveComplexity", "PMD.CyclomaticComplexity", "PMD.ExcessiveMethodLength", + "PMD.NcssCount", "PMD.NPathComplexity", "PMD.ShortMethodName"}) + public static AFGenericSocketAddress of(URI uri, int overridePort) throws SocketException { + throw new SocketException("Unsupported"); + } + + @Override + @SuppressWarnings({"PMD.CognitiveComplexity", "PMD.CompareObjectsWithEquals"}) + public URI toURI(String scheme, URI template) throws IOException { + return super.toURI(scheme, template); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketChannel.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketChannel.java new file mode 100644 index 000000000..794898810 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketChannel.java @@ -0,0 +1,30 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +/** + * A selectable channel for stream-oriented connecting sockets. + * + * @author Christian Kohlschütter + */ +final class AFGenericSocketChannel extends AFSocketChannel implements + AFGenericSocketExtensions { + AFGenericSocketChannel(AFGenericSocket socket) { + super(socket, AFGenericSelectorProvider.getInstance()); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketExtensions.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketExtensions.java new file mode 100644 index 000000000..dc9b4c965 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketExtensions.java @@ -0,0 +1,27 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +/** + * Defines certain methods that all junixsocket unknown-type socket implementations share and extend + * beyond the standard socket API. + * + * @author Christian Kohlschütter + */ +interface AFGenericSocketExtensions extends AFSocketExtensions { +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketFactory.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketFactory.java new file mode 100644 index 000000000..be60523f8 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketFactory.java @@ -0,0 +1,55 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.IOException; +import java.net.Socket; +import java.net.SocketException; + +/** + * The base for a SocketFactory that connects to unknown-type sockets. + */ +abstract class AFGenericSocketFactory extends AFSocketFactory { + /** + * Creates a {@link AFGenericSocketFactory}. + */ + protected AFGenericSocketFactory() { + super(AFGenericSocketAddress.class); + } + + @Override + public final Socket createSocket() throws SocketException { + return configure(AFGenericSocket.newInstance(this)); + } + + @Override + protected final AFGenericSocket connectTo(AFGenericSocketAddress addr) throws IOException { + return configure(AFGenericSocket.connectTo(addr)); + } + + /** + * Performs some optional configuration on a newly created socket. + * + * @param sock The socket. + * @return The very socket. + * @throws SocketException on error. + */ + protected AFGenericSocket configure(AFGenericSocket sock) throws SocketException { + return sock; + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketImpl.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketImpl.java new file mode 100644 index 000000000..a14c5db61 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketImpl.java @@ -0,0 +1,37 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.FileDescriptor; +import java.net.SocketException; + +final class AFGenericSocketImpl extends AFSocketImpl { + AFGenericSocketImpl(FileDescriptor fdObj) { + super(AFGenericSelectorProvider.AF_GENERIC, fdObj); + } + + @Override + public Object getOption(int optID) throws SocketException { + return getOptionLenient(optID); + } + + @Override + public void setOption(int optID, Object value) throws SocketException { + setOptionLenient(optID, value); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketImplExtensions.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketImplExtensions.java new file mode 100644 index 000000000..845e603df --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketImplExtensions.java @@ -0,0 +1,32 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +/** + * Code specific to generic-socket support that resides in the native library. To be used by + * {@code AFGenericSocket} and {@code AFGenericDatagramSocket} only. + * + * @author Christian Kohlschütter + */ +final class AFGenericSocketImplExtensions implements + AFSocketImplExtensions { + + @SuppressWarnings("PMD.UnusedFormalParameter") + AFGenericSocketImplExtensions(AncillaryDataSupport ancillaryDataSupport) { + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketOptions.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketOptions.java new file mode 100644 index 000000000..212464247 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketOptions.java @@ -0,0 +1,32 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Socket options for generic socket types. + * + * @author Christian Kohlschütter + */ +@NonNullByDefault +final class AFGenericSocketOptions { + private AFGenericSocketOptions() { + throw new IllegalStateException(); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketPair.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketPair.java new file mode 100644 index 000000000..1616a3b8d --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFGenericSocketPair.java @@ -0,0 +1,58 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.IOException; + +/** + * A pair of sockets. + * + * @param The socket type. + * @author Christian Kohlschütter + */ +final class AFGenericSocketPair extends AFSocketPair { + /** + * Creates a new socket pair. + * + * @param socket1 The first socket. + * @param socket2 The second socket. + */ + AFGenericSocketPair(T socket1, T socket2) { + super(socket1, socket2); + } + + /** + * Opens a socket pair of interconnected channels. + * + * @return The new channel pair. + * @throws IOException on error. + */ + public static AFGenericSocketPair open() throws IOException { + return AFGenericSelectorProvider.provider().openSocketChannelPair(); + } + + /** + * Opens a socket pair of interconnected datagram channels. + * + * @return The new channel pair. + * @throws IOException on error. + */ + public static AFGenericSocketPair openDatagram() throws IOException { + return AFGenericSelectorProvider.provider().openDatagramChannelPair(); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFIOSupplier.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFIOSupplier.java new file mode 100644 index 000000000..4ce6847ec --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFIOSupplier.java @@ -0,0 +1,37 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.IOException; + +/** + * A supplier that can throw an IOException. + * + * @param the type of results supplied by this supplier + */ +@FunctionalInterface +public interface AFIOSupplier { + + /** + * Gets a result. + * + * @return a result + * @throws IOException on error. + */ + T get() throws IOException; +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFInetAddress.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFInetAddress.java index 311e428d6..2792053a4 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFInetAddress.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFInetAddress.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,6 @@ */ package org.newsclub.net.unix; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.ObjectInputStream; import java.io.UnsupportedEncodingException; import java.net.DatagramPacket; import java.net.Inet4Address; @@ -36,56 +33,21 @@ /** * A workaround to create an {@link InetAddress} for an {@link AFSocketAddress}. - * + * * {@link DatagramPacket} internally requires InetAddress compatibility. Even if it pretends to * accept {@link SocketAddress}es, it refuses anything other than {@link InetSocketAddress} * and then even stores host and port separately. - * + * * This implementation deserializes a specially crafted {@link InetAddress} with a hostname that * encodes the raw bytes of an {@link AFSocketAddress}. We do this because the deserialization code * path does not attempt DNS resolution (which would fail one way or another). - * + * * The hostnames we use end with ".junixsocket", to distinguish them from regular hostnames. - * + * * @author Christian Kohlschütter */ class AFInetAddress { - private static final byte[] SERIALIZED_INET_ADDRESS_START = { - (byte) 0xac, (byte) 0xed, // STREAM_MAGIC - 0x00, 0x05, // STREAM_VERSION - 0x73, // TC_OBJECT - 0x72, // TC_CLASSDESC, - 0x00, 0x14, // length - 'j', 'a', 'v', 'a', '.', 'n', 'e', 't', '.', // - 'I', 'n', 'e', 't', 'A', 'd', 'd', 'r', 'e', 's', 's', // "java.net.InetAddress" - - 0x2d, (byte) 0x9b, 0x57, (byte) 0xaf, (byte) 0x9f, (byte) 0xe3, (byte) 0xeb, (byte) 0xdb, // - // serialVersionUID for java.net.InetAddress - - 0x03, // classDescFlags SC_WRITE_METHOD | SC_SERIALIZABLE - 0x00, 0x03, // fieldCount (3) - 'I', // int field - 0x00, 0x07, // length (7) - 'a', 'd', 'd', 'r', 'e', 's', 's', // "address" - 'I', // int field - 0x00, 0x06, // length (6) - 'f', 'a', 'm', 'i', 'l', 'y', // "family" - 'L', // Object field - 0x00, 0x08, // length (8) - 'h', 'o', 's', 't', 'N', 'a', 'm', 'e', // "hostName" - 0x74, // (className1) TC_STRING - 0x00, 0x12, // length (18) - 'L', 'j', 'a', 'v', 'a', '/', 'l', 'a', 'n', 'g', '/', // - 'O', 'b', 'j', 'e', 'c', 't', ';', // "Ljava/lang/Object;" - 0x78, // TC_ENDBLOCKDATA, - 0x70, // (superClassDesc) TC_NULL - 0x7f, 0x00, 0x00, (byte) 0xaf, // "address" value: (int) 127.0.0.175 (0x7f0000af) - 0x00, 0x00, 0x00, 0x01, // "family" value: (int) 1 (IPv4) - 0x74, // "hostName" value is a TC_STRING - 0x00, // high-byte of string length (always 0 in our case) - // low-byte of string length and string itself will be appended later - // followed by 0x78 (TC_ENDBLOCKDATA) - }; + private static final byte[] LOCAL_AF = {0x7f, 0, 0, (byte) 0xaf}; private static final char PREFIX = '['; private static final String MARKER_HEX_ENCODING = "%%"; @@ -94,12 +56,12 @@ class AFInetAddress { /** * Encodes a junixsocket socketAddress into a string that is (somewhat) guaranteed to not be * resolved by java.net code. - * + * * Implementation detail: The "[" prefix (with the corresponding "]" suffix missing from the * input) should cause an early {@link UnknownHostException} be thrown, which is caught within * {@link InetSocketAddress#InetSocketAddress(String, int)}, causing the hostname be marked as * "unresolved" (without an address set). - * + * * @param socketAddress The socket address. * @return A string, to be used when calling * {@link InetSocketAddress#InetSocketAddress(String, int)}, etc. @@ -140,8 +102,8 @@ static final String createUnresolvedHostname(byte[] socketAddress, AFAddressFami * Creates an InetAddress that is considered "resolved" internally (using a static loopback * address), without actually having to resolve the address via DNS, thus still carrying the * "hostname" field containing a hostname as returned by - * {@link #createUnresolvedHostname(byte[])}. - * + * {@link #createUnresolvedHostname(byte[],AFAddressFamily)}. + * * @param socketAddress The socket address. * @return The {@link InetAddress}. */ @@ -151,21 +113,15 @@ static final InetAddress wrapAddress(byte[] socketAddress, AFAddressFamily af return null; } - byte[] bytes = createUnresolvedHostname(socketAddress, af).getBytes(StandardCharsets.UTF_8); + String hostname = createUnresolvedHostname(socketAddress, af); + byte[] bytes = hostname.getBytes(StandardCharsets.UTF_8); if (bytes.length > 255) { throw new IllegalStateException("junixsocket address is too long to wrap as InetAddress"); } - byte[] serializedData = new byte[SERIALIZED_INET_ADDRESS_START.length + 1 + bytes.length + 1]; - System.arraycopy(SERIALIZED_INET_ADDRESS_START, 0, serializedData, 0, - SERIALIZED_INET_ADDRESS_START.length); - serializedData[SERIALIZED_INET_ADDRESS_START.length] = (byte) bytes.length; - System.arraycopy(bytes, 0, serializedData, SERIALIZED_INET_ADDRESS_START.length + 1, - bytes.length); - serializedData[serializedData.length - 1] = 0x78; // TC_ENDBLOCKDATA - - try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(serializedData))) { - return (InetAddress) ois.readObject(); - } catch (ClassNotFoundException | IOException e) { + + try { + return InetAddress.getByAddress(hostname, LOCAL_AF); + } catch (UnknownHostException e) { throw new IllegalStateException(e); } } @@ -182,7 +138,7 @@ static final byte[] unwrapAddress(InetAddress addr, AFAddressFamily af) try { return unwrapAddress(hostname, af); } catch (IllegalArgumentException e) { - throw (SocketException)new SocketException("Unsupported address").initCause(e); + throw (SocketException) new SocketException("Unsupported address").initCause(e); } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFInputStream.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFInputStream.java index dc2423e4d..cfa058dc3 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFInputStream.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFInputStream.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ /** * An {@link InputStream} for {@link AFSocket}, etc. - * + * * @author Christian Kohlschütter */ public abstract class AFInputStream extends InputStream implements FileDescriptorAccess { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFOutputStream.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFOutputStream.java index 531cbbb1f..7e0a10c4c 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFOutputStream.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFOutputStream.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,11 +21,14 @@ import java.io.InputStream; import java.io.OutputStream; +import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; + /** * An {@link OutputStream} for {@link AFSocket}, etc. - * + * * @author Christian Kohlschütter */ +@IgnoreJRERequirement // see src/main/java8 public abstract class AFOutputStream extends OutputStream implements FileDescriptorAccess { AFOutputStream() { super(); @@ -37,11 +40,11 @@ public abstract class AFOutputStream extends OutputStream implements FileDescrip * Reads all bytes from the given input stream and writes the bytes to this output stream in the * order that they are read. On return, this input stream will be at end of stream. This method * does not close either stream. - * + * * This method effectively is the reverse notation of * {@link InputStream#transferTo(OutputStream)}, which may or may not be optimized for * {@link AFSocket}s. - * + * * @param in The {@link InputStream} to transfer from. * @return The number of bytes transferred. * @throws IOException on error. diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFPipe.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFPipe.java index 15f733eee..28b9b910d 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFPipe.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFPipe.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,10 +28,11 @@ /** * A {@link Pipe}, natively implemented. - * + * * @author Christian Kohlschütter */ public final class AFPipe extends Pipe implements Closeable { + static final AFSupplier DUMMY_TIMEOUT = () -> 0; // wait forever private final AFCore sourceCore; private final AFCore sinkCore; private final SourceChannel sourceChannel; @@ -75,7 +76,7 @@ FileDescriptor sinkFD() { @Override public void close() throws IOException { - try { + try { // NOPMD.UseTryWithResources source().close(); } finally { sink().close(); @@ -107,7 +108,7 @@ public long read(ByteBuffer[] dsts) throws IOException { @Override public int read(ByteBuffer dst) throws IOException { - return sourceCore.read(dst, null, options); + return sourceCore.read(dst, DUMMY_TIMEOUT, null, options); } @Override @@ -151,7 +152,7 @@ public long write(ByteBuffer[] srcs) throws IOException { @Override public int write(ByteBuffer src) throws IOException { - return sinkCore.write(src, null, options); + return sinkCore.write(src, DUMMY_TIMEOUT, null, options); } @Override @@ -172,7 +173,7 @@ public FileDescriptor getFileDescriptor() throws IOException { /** * Returns the options bitmask that is to be passed to native receive/send calls. - * + * * @return The options. */ int getOptions() { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFProtocolFamily.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFProtocolFamily.java new file mode 100644 index 000000000..d80827cd7 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFProtocolFamily.java @@ -0,0 +1,53 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.IOException; +import java.net.ProtocolFamily; + +/** + * A junixsocket-based protocol family. + * + * @author Christian Kohlschütter + */ +public interface AFProtocolFamily extends ProtocolFamily { + + /** + * Creates a new datagram channel compatible with this protocol family. + * + * @return A new datagram channel. + * @throws IOException on error. + */ + AFDatagramChannel openDatagramChannel() throws IOException; + + /** + * Creates a new server socket channel compatible with this protocol family. + * + * @return A new server socket channel. + * @throws IOException on error. + */ + AFServerSocketChannel openServerSocketChannel() throws IOException; + + /** + * Creates a new socket channel compatible with this protocol family. + * + * @return A new socket channel. + * @throws IOException on error. + */ + AFSocketChannel openSocketChannel() throws IOException; +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSYSTEMSocketAddress.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSYSTEMSocketAddress.java new file mode 100644 index 000000000..c9bae46ff --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSYSTEMSocketAddress.java @@ -0,0 +1,408 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.InetAddress; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.newsclub.net.unix.pool.ObjectPool.Lease; + +/** + * An {@link AFSocketAddress} for AF_SYSTEM sockets. + * + * @author Christian Kohlschütter + */ +public final class AFSYSTEMSocketAddress extends AFSocketAddress { + private static final long serialVersionUID = 1L; // do not change! + + private static AFAddressFamily afSystem; + private static final String SELECTOR_PROVIDER_CLASS = + "org.newsclub.net.unix.darwin.system.AFSYSTEMSelectorProvider"; + + /** + * The AF_SYSTEM system address. + * + * @author Christian Kohlschütter + */ + @NonNullByDefault + public static final class SysAddr extends NamedInteger { + private static final long serialVersionUID = 1L; + + /** + * The sysaddr AF_SYS_CONTROL, also known as AF_SYS_KERN_CONTROL. + */ + public static final SysAddr AF_SYS_CONTROL; + + private static final @NonNull SysAddr[] VALUES = init(new @NonNull SysAddr[] { + AF_SYS_CONTROL = new SysAddr("AF_SYS_CONTROL", 2) // + }); + + private SysAddr(int id) { + super(id); + } + + private SysAddr(String name, int id) { + super(name, id); + } + + /** + * Returns a {@link SysAddr} for the given custom value. + * + * @param v The value. + * @return The {@link SysAddr} object. + */ + public static SysAddr ofValue(int v) { + return ofValue(VALUES, SysAddr::new, v); + } + } + + private AFSYSTEMSocketAddress(int port, final byte[] socketAddress, + Lease nativeAddress) throws SocketException { + super(port, socketAddress, nativeAddress, addressFamily()); + } + + private static AFSYSTEMSocketAddress newAFSocketAddress(int port, final byte[] socketAddress, + Lease nativeAddress) throws SocketException { + return newDeserializedAFSocketAddress(port, socketAddress, nativeAddress, addressFamily(), + AFSYSTEMSocketAddress::new); + } + + /** + * Returns an {@link AFSYSTEMSocketAddress} that refers to a given AF_SYSTEM socket address (i.e., + * referring to a particular socket instance instead of a service address). A Java-only "IP port + * number" is stored along the instance for compatibility reasons. + * + * @param javaPort The emulated "port" number (not part of AF_SYSTEM). + * @param sysAddr 16-bit system address (e.g., AF_SYS_KERNCONTROL) + * @param id Controller unique identifier + * @param unit Developer private unit number, 0 means "unspecified". + * @return A corresponding {@link AFSYSTEMSocketAddress} instance. + * @throws SocketException if the operation fails. + */ + public static AFSYSTEMSocketAddress ofSysAddrIdUnit(int javaPort, SysAddr sysAddr, int id, + int unit) throws SocketException { + return resolveAddress(toBytes(sysAddr, id, unit), javaPort, addressFamily()); + } + + /** + * Returns an {@link AFSYSTEMSocketAddress} that refers to a given AF_SYSTEM socket address (i.e., + * referring to a particular socket instance instead of a service address). + * + * @param sysAddr 16-bit system address (e.g., AF_SYS_KERNCONTROL) + * @param id Controller unique identifier + * @param unit Developer private unit number + * @return A corresponding {@link AFSYSTEMSocketAddress} instance. + * @throws SocketException if the operation fails. + */ + public static AFSYSTEMSocketAddress ofSysAddrIdUnit(SysAddr sysAddr, int id, int unit) + throws SocketException { + return ofSysAddrIdUnit(0, sysAddr, id, unit); + } + + /** + * Returns an {@link AFSYSTEMSocketAddress} given a special {@link InetAddress} that encodes the + * byte sequence of an AF_SYSTEM socket address, like those returned by {@link #wrapAddress()}. + * + * @param address The "special" {@link InetAddress}. + * @param port The port (use 0 for "none"). + * @return The {@link AFSYSTEMSocketAddress} instance. + * @throws SocketException if the operation fails, for example when an unsupported address is + * specified. + */ + public static AFSYSTEMSocketAddress unwrap(InetAddress address, int port) throws SocketException { + return AFSocketAddress.unwrap(address, port, addressFamily()); + } + + /** + * Returns an {@link AFSYSTEMSocketAddress} given a special {@link InetAddress} hostname that + * encodes the byte sequence of an AF_SYSTEM socket address, like those returned by + * {@link #wrapAddress()}. + * + * @param hostname The "special" hostname, as provided by {@link InetAddress#getHostName()}. + * @param port The port (use 0 for "none"). + * @return The {@link AFSYSTEMSocketAddress} instance. + * @throws SocketException if the operation fails, for example when an unsupported address is + * specified. + */ + public static AFSYSTEMSocketAddress unwrap(String hostname, int port) throws SocketException { + return AFSocketAddress.unwrap(hostname, port, addressFamily()); + } + + /** + * Returns an {@link AFSYSTEMSocketAddress} given a generic {@link SocketAddress}. + * + * @param address The address to unwrap. + * @return The {@link AFSYSTEMSocketAddress} instance. + * @throws SocketException if the operation fails, for example when an unsupported address is + * specified. + */ + public static AFSYSTEMSocketAddress unwrap(SocketAddress address) throws SocketException { + Objects.requireNonNull(address); + if (!isSupportedAddress(address)) { + throw new SocketException("Unsupported address"); + } + return (AFSYSTEMSocketAddress) address; + } + + @Override + public String toString() { + int port = getPort(); + + byte[] bytes = getBytes(); + if (bytes.length != (8 * 4)) { + return getClass().getName() + "[" + (port == 0 ? "" : "port=" + port) + ";UNKNOWN" + "]"; + } + + ByteBuffer bb = ByteBuffer.wrap(bytes); + SysAddr sysAddr = SysAddr.ofValue(bb.getInt()); + int id = bb.getInt(); + int unit = bb.getInt(); + + return getClass().getName() + "[" + (port == 0 ? "" : "port=" + port + ";") + sysAddr + ";id=" + + id + ";unit=" + unit + "]"; + } + + /** + * Returns the "SysAddr" part of the address. + * + * @return The SysAddr part. + */ + public @NonNull SysAddr getSysAddr() { + ByteBuffer bb = ByteBuffer.wrap(getBytes()); + return SysAddr.ofValue(bb.getInt(0)); + } + + /** + * Returns the "id" part of the address. + * + * @return The id part. + */ + public int getId() { + ByteBuffer bb = ByteBuffer.wrap(getBytes()); + return bb.getInt(4); + } + + /** + * Returns the "unit" part of the address. + * + * @return The unit part. + */ + public int getUnit() { + ByteBuffer bb = ByteBuffer.wrap(getBytes()); + return bb.getInt(8); + } + + @Override + public boolean hasFilename() { + return false; + } + + @Override + public File getFile() throws FileNotFoundException { + throw new FileNotFoundException("no file"); + } + + /** + * Checks if an {@link InetAddress} can be unwrapped to an {@link AFSYSTEMSocketAddress}. + * + * @param addr The instance to check. + * @return {@code true} if so. + * @see #wrapAddress() + * @see #unwrap(InetAddress, int) + */ + public static boolean isSupportedAddress(InetAddress addr) { + return AFSocketAddress.isSupportedAddress(addr, addressFamily()); + } + + /** + * Checks if a {@link SocketAddress} can be unwrapped to an {@link AFSYSTEMSocketAddress}. + * + * @param addr The instance to check. + * @return {@code true} if so. + * @see #unwrap(InetAddress, int) + */ + public static boolean isSupportedAddress(SocketAddress addr) { + return (addr instanceof AFSYSTEMSocketAddress); + } + + @SuppressWarnings("cast") + private static byte[] toBytes(SysAddr sysAddr, int id, int unit) { + ByteBuffer bb = ByteBuffer.allocate(8 * 4); + bb.putInt(sysAddr.value()); + bb.putInt(id); + bb.putInt(unit); + bb.putInt(0); + bb.putInt(0); + bb.putInt(0); + bb.putInt(0); + bb.putInt(0); + return (byte[]) bb.flip().array(); + } + + /** + * Returns the corresponding {@link AFAddressFamily}. + * + * @return The address family instance. + */ + @SuppressWarnings("null") + public static synchronized AFAddressFamily addressFamily() { + if (afSystem == null) { + afSystem = AFAddressFamily.registerAddressFamily("system", // + AFSYSTEMSocketAddress.class, new AFSocketAddressConfig() { + + private final AFSocketAddressConstructor addrConstr = + isUseDeserializationForInit() ? AFSYSTEMSocketAddress::newAFSocketAddress + : AFSYSTEMSocketAddress::new; + + @Override + protected AFSYSTEMSocketAddress parseURI(URI u, int port) throws SocketException { + return AFSYSTEMSocketAddress.of(u, port); + } + + @Override + protected AFSocketAddressConstructor addressConstructor() { + return addrConstr; + } + + @Override + protected String selectorProviderClassname() { + return SELECTOR_PROVIDER_CLASS; + } + + @Override + protected Set uriSchemes() { + return new HashSet<>(Arrays.asList("afsystem")); + } + }); + try { + Class.forName(SELECTOR_PROVIDER_CLASS); + } catch (ClassNotFoundException e) { + // ignore + } + } + return afSystem; + } + + /** + * Returns an {@link AFSYSTEMSocketAddress} for the given URI, if possible. + * + * @param uri The URI. + * @return The address. + * @throws SocketException if the operation fails. + */ + @SuppressWarnings("PMD.ShortMethodName") + public static AFSYSTEMSocketAddress of(URI uri) throws SocketException { + return of(uri, -1); + } + + /** + * Returns an {@link AFSYSTEMSocketAddress} for the given URI, if possible. + * + * @param uri The URI. + * @param overridePort The port to forcibly use, or {@code -1} for "don't override". + * @return The address. + * @throws SocketException if the operation fails. + */ + @SuppressWarnings({ + "PMD.CognitiveComplexity", "PMD.CyclomaticComplexity", "PMD.ExcessiveMethodLength", + "PMD.NcssCount", "PMD.NPathComplexity", "PMD.ShortMethodName"}) + public static AFSYSTEMSocketAddress of(URI uri, int overridePort) throws SocketException { + switch (uri.getScheme()) { + case "afsystem": + break; + default: + throw new SocketException("Unsupported URI scheme: " + uri.getScheme()); + } + + String host; + if ((host = uri.getHost()) == null || host.isEmpty()) { + String ssp = uri.getSchemeSpecificPart(); + if (ssp == null || !ssp.startsWith("//")) { + throw new SocketException("Unsupported URI: " + uri); + } + ssp = ssp.substring(2); + int i = ssp.indexOf('/'); + host = i == -1 ? ssp : ssp.substring(0, i); + if (host.isEmpty()) { + throw new SocketException("Unsupported URI: " + uri); + } + } + + ByteBuffer bb = ByteBuffer.allocate(8 * 4); + for (String p : host.split("\\.")) { + int v; + try { + v = parseUnsignedInt(p, 10); + } catch (NumberFormatException e) { + throw (SocketException) new SocketException("Unsupported URI: " + uri).initCause(e); + } + bb.putInt(v); + } + bb.flip(); + if (bb.remaining() > 8 * 4) { + throw new SocketException("Unsupported URI: " + uri); + } + /* SysAddr sa = */ SysAddr.ofValue(bb.getInt()); + /* int id = */ bb.getInt(); + /* int unit = */ bb.getInt(); + + while (bb.remaining() > 0) { + if (bb.getInt() != 0) { + throw new SocketException("Unsupported URI: " + uri); + } + } + + return + + resolveAddress(bb.array(), uri.getPort(), addressFamily()); + } + + @Override + @SuppressWarnings({"PMD.CognitiveComplexity", "PMD.CompareObjectsWithEquals"}) + public URI toURI(String scheme, URI template) throws IOException { + switch (scheme) { + case "afsystem": + break; + default: + return super.toURI(scheme, template); + } + + ByteBuffer bb = ByteBuffer.wrap(getBytes()); + StringBuilder sb = new StringBuilder(); + while (bb.remaining() > 0) { + sb.append(toUnsignedString(bb.getInt())); + if (bb.remaining() > 0) { + sb.append('.'); + } + } + + return new HostAndPort(sb.toString(), getPort()).toURI(scheme, template); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSYSTEMSocketImplExtensions.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSYSTEMSocketImplExtensions.java new file mode 100644 index 000000000..44899c259 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSYSTEMSocketImplExtensions.java @@ -0,0 +1,48 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.util.Objects; + +/** + * AF_SYSTEM-specific code that resides in the native library. To be used by {@code AFSystemSocket} + * and {@code AFSystemDatagramSocket} only. + * + * @author Christian Kohlschütter + */ +public final class AFSYSTEMSocketImplExtensions implements + AFSocketImplExtensions { + + @SuppressWarnings("PMD.UnusedFormalParameter") + AFSYSTEMSocketImplExtensions(AncillaryDataSupport ancillaryDataSupport) { + } + + /** + * Retrieves the kernel control ID given a kernel control name. + * + * @param fd The socket file descriptor. + * @param name The control name + * @return The control Id. + * @throws IOException on error. + */ + public int getKernelControlId(FileDescriptor fd, String name) throws IOException { + return NativeUnixSocket.systemResolveCtlId(fd, Objects.requireNonNull(name)); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSelectionKey.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSelectionKey.java index a70352a4f..d7e892089 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSelectionKey.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSelectionKey.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,7 +45,7 @@ final class AFSelectionKey extends SelectionKey { } else if (ch instanceof AFServerSocketChannel) { this.core = ((AFServerSocketChannel) ch).getAFCore(); } else { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Unsupported channel: " + ch); } attach(att); @@ -66,10 +66,18 @@ public boolean isValid() { return !hasOpInvalid() && !cancelled.get() && chann.isOpen() && sel.isOpen(); } + boolean isCancelled() { + return cancelled.get(); + } + boolean hasOpInvalid() { return (opsReady & OP_INVALID) != 0; } + boolean isSelected() { + return readyOps() != 0; + } + @Override public void cancel() { sel.remove(this); diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSelector.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSelector.java index fa4c56040..ed8897a95 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSelector.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSelector.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,12 +28,12 @@ import java.nio.channels.Selector; import java.nio.channels.spi.AbstractSelectableChannel; import java.nio.channels.spi.AbstractSelector; -import java.util.AbstractSet; -import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; final class AFSelector extends AbstractSelector { private final AFPipe selectorPipe; @@ -42,12 +42,21 @@ final class AFSelector extends AbstractSelector { private final ByteBuffer pipeMsgWakeUp = ByteBuffer.allocate(1); private final ByteBuffer pipeMsgReceiveBuffer = ByteBuffer.allocateDirect(256); - private final Set keysRegistered = new HashSet<>(); + private final Map keysRegistered = new ConcurrentHashMap<>(); + private final Set keysRegisteredKeySet = keysRegistered.keySet(); + private final Set keysRegisteredPublic = Collections.unmodifiableSet( + keysRegisteredKeySet); + + private final AtomicInteger selectCount = new AtomicInteger(0); + + @SuppressWarnings("PMD.LooseCoupling") + private final MapValueSet selectedKeysSet = + new MapValueSet(keysRegistered, selectCount::get, 0); + private final Set selectedKeysPublic = new UngrowableSet<>(selectedKeysSet); private PollFd pollFd = null; - private final Set keysSelected = new HashSet<>(); - protected AFSelector(AFSelectorProvider provider) throws IOException { + AFSelector(AFSelectorProvider provider) throws IOException { super(provider); this.selectorPipe = AFUNIXSelectorProvider.getInstance().openSelectablePipe(); @@ -59,23 +68,19 @@ protected SelectionKey register(AbstractSelectableChannel ch, int ops, Object at AFSelectionKey key = new AFSelectionKey(this, ch, ops, att); synchronized (this) { pollFd = null; - keysRegistered.add(key); + selectedKeysSet.markRemoved(key); } return key; } @Override public Set keys() { - synchronized (this) { - return Collections.unmodifiableSet(new HashSet<>(keysRegistered)); - } + return keysRegisteredPublic; } @Override public Set selectedKeys() { - synchronized (keysSelected) { - return new SelectionKeySet(keysSelected); - } + return selectedKeysPublic; } @Override @@ -106,14 +111,15 @@ public int select() throws IOException { @SuppressWarnings("PMD.CognitiveComplexity") private int select0(int timeout) throws IOException { PollFd pfd; + + int selectId = updateSelectCount(); + synchronized (this) { if (!isOpen()) { throw new ClosedSelectorException(); } + pfd = pollFd = initPollFd(pollFd); - synchronized (keysSelected) { - keysSelected.clear(); - } } int num; try { @@ -139,11 +145,9 @@ private int select0(int timeout) throws IOException { } if (num > 0) { consumeAllBytesAfterPoll(); - setOpsReady(); - } - synchronized (keysSelected) { - return keysSelected.size(); + setOpsReady(pfd, selectId); // updates keysSelected and numKeysSelected } + return selectedKeysSet.size(); } } @@ -162,8 +166,7 @@ private synchronized void consumeAllBytesAfterPoll() throws IOException { synchronized (pipeMsgReceiveBuffer) { pipeMsgReceiveBuffer.clear(); maxReceive = pipeMsgReceiveBuffer.remaining(); - bytesReceived = NativeUnixSocket.receive(pollFd.fds[0], pipeMsgReceiveBuffer, 0, maxReceive, - null, options, null, 1); + bytesReceived = receive(maxReceive, options); } if (bytesReceived == maxReceive && maxReceive > 0) { @@ -173,40 +176,96 @@ private synchronized void consumeAllBytesAfterPoll() throws IOException { if ((read = NativeUnixSocket.poll(selectorPipePollFd, 0)) > 0) { synchronized (pipeMsgReceiveBuffer) { pipeMsgReceiveBuffer.clear(); - read = NativeUnixSocket.receive(selectorPipePollFd.fds[0], pipeMsgReceiveBuffer, 0, - maxReceive, null, options, null, 1); + read = receive(maxReceive, options); } } } while (read == maxReceive && read > 0); } } - private synchronized void setOpsReady() { - if (pollFd == null) { - return; - } - for (int i = 1; i < pollFd.rops.length; i++) { - int rops = pollFd.rops[i]; - if (rops == 0) { - continue; + @SuppressWarnings("PMD.CognitiveComplexity") + private int receive(int maxReceive, int options) throws IOException { + final boolean virtualBlocking = ThreadUtil.isVirtualThread(); + final long now; + if (virtualBlocking) { + now = System.currentTimeMillis(); + options |= NativeUnixSocket.OPT_NON_BLOCKING; + } else { + now = 0; + } + + FileDescriptor fdesc = selectorPipePollFd.fds[0]; + + boolean park = false; + int count; + virtualThreadLoop : do { + if (virtualBlocking) { + if (park) { + VirtualThreadPoller.INSTANCE.parkThreadUntilReady(fdesc, SelectionKey.OP_WRITE, now, + AFPipe.DUMMY_TIMEOUT, this::close); + } + NativeUnixSocket.configureBlocking(fdesc, false); } - AFSelectionKey key = pollFd.keys[i]; - key.setOpsReady(rops); - synchronized (keysSelected) { - keysSelected.add(key); + try { + count = NativeUnixSocket.receive(fdesc, pipeMsgReceiveBuffer, 0, maxReceive, null, options, + null, 1); + if (count == 0 && virtualBlocking) { + // try again + park = true; + continue virtualThreadLoop; + } + } catch (SocketTimeoutException e) { + if (virtualBlocking) { + // try again + park = true; + continue virtualThreadLoop; + } else { + throw e; + } + } finally { + if (virtualBlocking) { + NativeUnixSocket.configureBlocking(fdesc, true); + } } + break; // NOPMD.AvoidBranchingStatementAsLastInLoop virtualThreadLoop + } while (true); // NOPMD.WhileLoopWithLiteralBoolean + return count; + } + + private int updateSelectCount() { + int selectId = selectCount.incrementAndGet(); + if (selectId == 0) { + // overflow (unlikely) + selectedKeysSet.markAllRemoved(); + selectId = selectCount.incrementAndGet(); } + return selectId; } - @SuppressWarnings("resource") + private void setOpsReady(PollFd pfd, int selectId) { + if (pfd != null) { + for (int i = 1; i < pfd.rops.length; i++) { + int rops = pfd.rops[i]; + AFSelectionKey key = pfd.keys[i]; + key.setOpsReady(rops); + if (rops != 0 && keysRegistered.containsKey(key)) { + keysRegistered.put(key, selectId); + } + } + } + } + + @SuppressWarnings({"resource", "PMD.CognitiveComplexity"}) private PollFd initPollFd(PollFd existingPollFd) throws IOException { synchronized (this) { - for (Iterator it = keysRegistered.iterator(); it.hasNext();) { + for (Iterator it = keysRegisteredKeySet.iterator(); it.hasNext();) { AFSelectionKey key = it.next(); - if (!key.getAFCore().fd.valid()) { + if (!key.getAFCore().fd.valid() || !key.isValid()) { key.cancelNoRemove(); it.remove(); existingPollFd = null; + } else { + key.setOpsReady(0); } } @@ -215,7 +274,7 @@ private PollFd initPollFd(PollFd existingPollFd) throws IOException { (existingPollFd.keys.length - 1) == keysRegistered.size()) { boolean needsUpdate = false; int i = 1; - for (AFSelectionKey key : keysRegistered) { + for (AFSelectionKey key : keysRegisteredKeySet) { if (existingPollFd.keys[i] != key || !key.isValid()) { // NOPMD needsUpdate = true; break; @@ -230,7 +289,14 @@ private PollFd initPollFd(PollFd existingPollFd) throws IOException { } } - int size = keysRegistered.size() + 1; + int keysToPoll = keysRegistered.size(); + for (AFSelectionKey key : keysRegisteredKeySet) { + if (!key.isValid()) { + keysToPoll--; + } + } + + int size = keysToPoll + 1; FileDescriptor[] fds = new FileDescriptor[size]; int[] ops = new int[size]; @@ -239,7 +305,10 @@ private PollFd initPollFd(PollFd existingPollFd) throws IOException { ops[0] = SelectionKey.OP_READ; int i = 1; - for (AFSelectionKey key : keysRegistered) { + for (AFSelectionKey key : keysRegisteredKeySet) { + if (!key.isValid()) { + continue; + } keys[i] = key; fds[i] = key.getAFCore().fd; ops[i] = key.interestOps(); @@ -279,20 +348,17 @@ public Selector wakeup() { } } } - } catch (IOException e) { + } catch (IOException e) { // NOPMD.ExceptionAsFlowControl // FIXME throw as runtimeexception? - e.printStackTrace(); + StackTraceUtil.printStackTrace(e); } } return this; } synchronized void remove(AFSelectionKey key) { + selectedKeysSet.remove(key); deregister(key); - keysRegistered.remove(key); - synchronized (keysSelected) { - keysSelected.remove(key); - } pollFd = null; } @@ -329,6 +395,10 @@ static final class PollFd { this.keys = null; } + PollFd(FileDescriptor[] fds, int[] ops) { + this(null, fds, ops); + } + @SuppressWarnings("PMD.ArrayIsStoredDirectly") PollFd(AFSelectionKey[] keys, FileDescriptor[] fds, int[] ops) { this.keys = keys; @@ -340,95 +410,4 @@ static final class PollFd { this.rops = new int[ops.length]; } } - - private static final class SelectionKeySet extends AbstractSet { - private final Set keys; - - SelectionKeySet(Set keys) { - super(); - this.keys = keys; - } - - @Override - public boolean add(SelectionKey e) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean addAll(Collection c) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean remove(Object o) { - synchronized (keys) { - return keys.remove(o); - } - } - - @Override - public Iterator iterator() { - final Set copy; - synchronized (keys) { - copy = new HashSet<>(keys); - } - Iterator it = copy.iterator(); - return new Iterator() { - SelectionKey current = null; - - @Override - public boolean hasNext() { - return it.hasNext(); - } - - @Override - public SelectionKey next() { - current = it.next(); - return current; - } - - @Override - public void remove() { - if (current != null) { - SelectionKeySet.this.remove(current); - } - } - }; - } - - @Override - public int size() { - synchronized (keys) { - return keys.size(); - } - } - - @Override - public boolean isEmpty() { - synchronized (keys) { - return keys.isEmpty(); - } - } - - @Override - public boolean contains(Object o) { - synchronized (keys) { - return keys.contains(o); - } - } - - @Override - public boolean containsAll(Collection c) { - synchronized (keys) { - return super.containsAll(c); - } - } - - @Override - public void clear() { - synchronized (keys) { - keys.clear(); - } - } - } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSelectorProvider.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSelectorProvider.java index 5fb4a4c16..d1c64be12 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSelectorProvider.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSelectorProvider.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,17 +20,82 @@ import java.io.IOException; import java.net.ProtocolFamily; import java.net.SocketAddress; +import java.net.StandardProtocolFamily; +import java.nio.channels.DatagramChannel; +import java.nio.channels.Pipe; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; import java.nio.channels.spi.AbstractSelector; import java.nio.channels.spi.SelectorProvider; +import java.util.Objects; import org.eclipse.jdt.annotation.NonNull; /** * Service-provider class for junixsocket selectors and selectable channels. - * + * * @param The concrete {@link AFSocketAddress} that is supported by this type. */ -public abstract class AFSelectorProvider extends SelectorProvider { +public abstract class AFSelectorProvider extends SelectorProviderShim { + private static final SelectorProvider AF_PROVIDER = new SelectorProviderShim() { + @Override + public SocketChannel openSocketChannel() throws IOException { + throw new UnsupportedOperationException( + "Use openSocketChannel(ProtocolFamily) or a specific AFSelectorProvider subclass"); + } + + @Override + public ServerSocketChannel openServerSocketChannel() throws IOException { + throw new UnsupportedOperationException( + "Use openServerSocketChannel(ProtocolFamily) or a specific AFSelectorProvider subclass"); + } + + @Override + public DatagramChannel openDatagramChannel() throws IOException { + throw new UnsupportedOperationException( + "Use openDatagramChannel(ProtocolFamily) or a specific AFSelectorProvider subclass"); + } + + @Override + public AbstractSelector openSelector() throws IOException { + throw new UnsupportedOperationException("Use a specific AFSelectorProvider subclass"); + } + + @Override + public Pipe openPipe() throws IOException { + throw new UnsupportedOperationException("Use a specific AFSelectorProvider subclass"); + } + + @Override + public DatagramChannel openDatagramChannel(ProtocolFamily family) throws IOException { + Objects.requireNonNull(family); + if (family instanceof AFProtocolFamily) { + return ((AFProtocolFamily) family).openDatagramChannel(); + } else { + throw new UnsupportedOperationException("Unsupported protocol family"); + } + } + + @Override + public SocketChannel openSocketChannel(ProtocolFamily family) throws IOException { + Objects.requireNonNull(family); + if (family instanceof AFProtocolFamily) { + return ((AFProtocolFamily) family).openSocketChannel(); + } else { + throw new UnsupportedOperationException("Unsupported protocol family"); + } + } + + @Override + public ServerSocketChannel openServerSocketChannel(ProtocolFamily family) throws IOException { + Objects.requireNonNull(family); + if (family instanceof AFProtocolFamily) { + return ((AFProtocolFamily) family).openServerSocketChannel(); + } else { + throw new UnsupportedOperationException("Unsupported protocol family"); + } + } + }; /** * Constructs a new {@link AFSelectorProvider}. @@ -41,7 +106,7 @@ protected AFSelectorProvider() { /** * Constructs a new pipe. - * + * * @param selectable {@code true} if the pipe should be selectable. * @return The pipe. * @throws IOException on error. @@ -52,7 +117,7 @@ private AFPipe newPipe(boolean selectable) throws IOException { /** * Constructs a new socket pair from two sockets. - * + * * @param The type of the pair. * @param s1 Some socket, the first one. * @param s2 Some socket, the second one. @@ -62,7 +127,7 @@ private AFPipe newPipe(boolean selectable) throws IOException { /** * Constructs a new socket. - * + * * @return The socket instance. * @throws IOException on error. */ @@ -70,21 +135,21 @@ private AFPipe newPipe(boolean selectable) throws IOException { /** * Returns the protocol family supported by this implementation. - * + * * @return The protocol family. */ protected abstract ProtocolFamily protocolFamily(); /** * Returns the address family supported by this implementation. - * + * * @return The address family. */ protected abstract AFAddressFamily<@NonNull A> addressFamily(); /** * Returns the domain ID for the supported protocol, as specified by {@link NativeUnixSocket}. - * + * * @return The domain ID. */ protected final int domainId() { @@ -93,7 +158,7 @@ protected final int domainId() { /** * Opens a socket pair of interconnected channels. - * + * * @return The new channel pair. * @throws IOException on error. */ @@ -113,18 +178,30 @@ public AFSocketPair> openSocketChannelPair() throws /** * Opens a socket pair of interconnected datagram channels. - * + * * @return The new channel pair. * @throws IOException on error. */ - @SuppressWarnings("resource") public AFSocketPair> openDatagramChannelPair() throws IOException { + return openDatagramChannelPair(AFSocketType.SOCK_DGRAM); + } + + /** + * Opens a socket pair of interconnected {@link DatagramChannel}s, using the given + * {@link AFSocketType}. + * + * @param type The socket type. + * @return The new channel pair. + * @throws IOException on error. + */ + @SuppressWarnings("resource") + public AFSocketPair> openDatagramChannelPair(AFSocketType type) + throws IOException { ProtocolFamily pf = protocolFamily(); AFDatagramChannel s1 = openDatagramChannel(pf); AFDatagramChannel s2 = openDatagramChannel(pf); - NativeUnixSocket.socketPair(domainId(), NativeUnixSocket.SOCK_STREAM, s1.getAFCore().fd, s2 - .getAFCore().fd); + NativeUnixSocket.socketPair(domainId(), type.getId(), s1.getAFCore().fd, s2.getAFCore().fd); s1.socket().internalDummyBind(); s2.socket().internalDummyBind(); @@ -137,13 +214,14 @@ public AFSocketPair> openDatagramChannelPair() th @Override public abstract AFDatagramChannel openDatagramChannel() throws IOException; - @Override - public AFDatagramChannel openDatagramChannel(ProtocolFamily family) throws IOException { - if (!protocolFamily().name().equals(family.name())) { - throw new UnsupportedOperationException("Unsupported protocol family"); - } - return openDatagramChannel(); - } + /** + * Opens a {@link DatagramChannel} using the given socket type. + * + * @param type The socket type. + * @return the new channel + * @throws IOException on error. + */ + public abstract AFDatagramChannel openDatagramChannel(AFSocketType type) throws IOException; @Override public final AFPipe openPipe() throws IOException { @@ -152,7 +230,7 @@ public final AFPipe openPipe() throws IOException { /** * Opens a pipe with support for selectors. - * + * * @return The new pipe * @throws IOException on error. */ @@ -185,10 +263,60 @@ public AFSocketChannel openSocketChannel() throws IOException { /** * Opens a socket channel connected to the given {@link SocketAddress}. - * + * * @param sa The socket address to connect to. * @return The new channel * @throws IOException on error. */ public abstract AFSocketChannel openSocketChannel(SocketAddress sa) throws IOException; + + @Override + public AFSocketChannel openSocketChannel(ProtocolFamily family) throws IOException { + Objects.requireNonNull(family); + + // Workaround for StandardProtocolFamily.UNIX check on older Java versions + if (protocolFamily().equals(family) || (family instanceof StandardProtocolFamily + && protocolFamily().name().equals(family.name()))) { + return openSocketChannel(); + } + + throw new UnsupportedOperationException("Protocol family not supported"); + } + + @Override + public AFServerSocketChannel openServerSocketChannel(ProtocolFamily family) + throws IOException { + Objects.requireNonNull(family); + + // Workaround for StandardProtocolFamily.UNIX check on older Java versions + if (protocolFamily().equals(family) || (family instanceof StandardProtocolFamily + && protocolFamily().name().equals(family.name()))) { + return openServerSocketChannel(); + } + + throw new UnsupportedOperationException("Protocol family not supported"); + } + + @Override + public AFDatagramChannel openDatagramChannel(ProtocolFamily family) throws IOException { + Objects.requireNonNull(family); + + // Workaround for StandardProtocolFamily.UNIX check on older Java versions + if (protocolFamily().equals(family) || (family instanceof StandardProtocolFamily + && protocolFamily().name().equals(family.name()))) { + return openDatagramChannel(); + } + + throw new UnsupportedOperationException("Protocol family not supported"); + } + + /** + * Returns the singleton instance for an "fallback" provider that supports the methods taking + * {@link ProtocolFamily}, as long as a junixsocket-specific {@link AFProtocolFamily} is used. + * + * @return The instance. + */ + public static SelectorProvider provider() { + return AF_PROVIDER; + } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFServerSocket.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFServerSocket.java index d99fb104a..2b2ba2012 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFServerSocket.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFServerSocket.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,10 +21,15 @@ import java.io.File; import java.io.FileDescriptor; import java.io.IOException; +import java.net.InetAddress; import java.net.ServerSocket; import java.net.SocketAddress; import java.net.SocketException; +import java.net.SocketOption; +import java.net.SocketOptions; import java.nio.channels.IllegalBlockingModeException; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jdt.annotation.NonNull; @@ -34,29 +39,34 @@ /** * The server part of a junixsocket socket. - * + * * @param The concrete {@link AFSocketAddress} that is supported by this type. * @author Christian Kohlschütter */ +@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.CouplingBetweenObjects"}) public abstract class AFServerSocket extends ServerSocket implements - FileDescriptorAccess { + AFSomeSocketThing { private final AFSocketImpl implementation; private @Nullable A boundEndpoint; private final Closeables closeables = new Closeables(); private final AtomicBoolean created = new AtomicBoolean(false); private final AtomicBoolean deleteOnClose = new AtomicBoolean(true); - private final AFServerSocketChannel channel = newChannel(); + + @SuppressWarnings("this-escape") + private final AFServerSocketChannel channel = newChannel(); private @Nullable SocketAddressFilter bindFilter; + private final AtomicBoolean closed = new AtomicBoolean(false); + /** * The constructor of the concrete subclass. - * + * * @param The concrete {@link AFSocketAddress} that is supported by this type. */ public interface Constructor { /** * Creates a new {@link AFServerSocket} instance. - * + * * @param fd The file descriptor. * @return The new instance. * @throws IOException on error. @@ -67,37 +77,41 @@ public interface Constructor { /** * Constructs a new, unconnected instance. - * + * * @throws IOException if the operation fails. */ + @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") protected AFServerSocket() throws IOException { this(null); } /** * Constructs a new instance, optionally associated with the given file descriptor. - * + * * @param fdObj The file descriptor, or {@code null}. * @throws IOException if the operation fails. */ + @SuppressWarnings({"this-escape", "PMD.ConstructorCallsOverridableMethod"}) + @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") protected AFServerSocket(FileDescriptor fdObj) throws IOException { super(); + this.implementation = newImpl(fdObj); NativeUnixSocket.initServerImpl(this, implementation); - setReuseAddress(true); + getAFImpl().setOption(SocketOptions.SO_REUSEADDR, true); } /** * Creates a new AFServerSocketChannel for this socket. - * + * * @return The new instance. */ - protected abstract AFServerSocketChannel newChannel(); + protected abstract AFServerSocketChannel newChannel(); /** * Creates a new AFSocketImpl. - * + * * @param fdObj The file descriptor. * @return The new instance. * @throws IOException on error. @@ -106,7 +120,7 @@ protected AFServerSocket(FileDescriptor fdObj) throws IOException { /** * Creates a new AFServerSocket instance, using the given subclass constructor. - * + * * @param The concrete {@link AFSocketAddress} that is supported by this type. * @param instanceSupplier The subclass constructor. * @return The new instance. @@ -119,7 +133,7 @@ protected static AFServerSocket newInstance( /** * Creates a new AFServerSocket instance, using the given subclass constructor. - * + * * @param The concrete {@link AFSocketAddress} that is supported by this type. * @param instanceSupplier The subclass constructor. * @param fdObj The file descriptor. @@ -163,7 +177,7 @@ protected static AFServerSocket newInstance( /** * Returns a new {@link ServerSocket} that is bound to the given {@link AFSocketAddress}. - * + * * @param instanceSupplier The constructor of the concrete subclass. * @param addr The socket file to bind to. * @param The concrete {@link AFSocketAddress} that is supported by this type. @@ -179,7 +193,7 @@ protected static AFServerSocket bindOn( /** * Returns a new {@link ServerSocket} that is bound to the given {@link AFSocketAddress}. - * + * * @param instanceSupplier The constructor of the concrete subclass. * @param addr The socket file to bind to. * @param deleteOnClose If {@code true}, the socket file (if the address points to a file) will be @@ -199,7 +213,7 @@ protected static AFServerSocket bindOn( /** * Returns a new, unbound {@link ServerSocket} that will always bind to the given * address, regardless of any socket address used in a call to bind. - * + * * @param instanceSupplier The constructor of the concrete subclass. * @param forceAddr The address to use. * @param The concrete {@link AFSocketAddress} that is supported by this type. @@ -216,7 +230,7 @@ protected static AFServerSocket forceBindOn( * Forces the address to be used for any subsequent call to {@link #bind(SocketAddress)} to be the * given one, regardless of what'll be passed to {@link #bind(SocketAddress, int)}, but doesn't * bind yet. - * + * * @param endpoint The forced endpoint address. * @return This {@link AFServerSocket}. */ @@ -226,6 +240,11 @@ public final AFServerSocket forceBindAddress(SocketAddress endpoint) { }); } + @Override + public final void bind(SocketAddress endpoint) throws IOException { + bind(endpoint, 50); + } + @SuppressWarnings("unchecked") @Override public final void bind(SocketAddress endpoint, int backlog) throws IOException { @@ -241,10 +260,7 @@ public final void bind(SocketAddress endpoint, int backlog) throws IOException { bindErrorOk = false; } - if (!(endpoint instanceof AFSocketAddress)) { - throw new IllegalArgumentException("Can only bind to endpoints of type " - + AFSocketAddress.class.getName() + ": " + endpoint); - } + endpoint = AFSocketAddress.mapOrFail(endpoint); A endpointCast; try { @@ -264,7 +280,7 @@ public final void bind(SocketAddress endpoint, int backlog) throws IOException { } } setBoundEndpoint(getAFImpl().getLocalSocketAddress()); - if (boundEndpoint == null) { + if (boundEndpoint0() == null) { setBoundEndpoint(endpointCast); } @@ -277,7 +293,7 @@ public final void bind(SocketAddress endpoint, int backlog) throws IOException { @Override public final boolean isBound() { - return boundEndpoint != null && implementation.getFD().valid(); + return boundEndpoint0() != null && implementation.getFD().valid(); } @Override @@ -297,7 +313,7 @@ AFSocket accept1(boolean throwOnFail) throws IOException { boolean success = implementation.accept0(as.getAFImpl(false)); if (isClosed()) { // We may have connected to the socket to unblock it - throw new SocketClosedException("Socket is closed"); + throw new BrokenPipeSocketException("Socket is closed"); } if (!success) { @@ -323,7 +339,7 @@ AFSocket accept1(boolean throwOnFail) throws IOException { /** * Returns a new {@link AFSocket} instance. - * + * * @return The new instance. * @throws IOException on error. */ @@ -331,11 +347,14 @@ AFSocket accept1(boolean throwOnFail) throws IOException { @Override public String toString() { - return getClass().getSimpleName() + "[" + (isBound() ? boundEndpoint : "unbound") + "]"; + return getClass().getSimpleName() + "[" + (isBound() ? boundEndpoint0() : "unbound") + "]"; } @Override - public synchronized void close() throws IOException { + public void close() throws IOException { + if (!closed.compareAndSet(false, true)) { + return; + } if (isClosed()) { return; } @@ -381,7 +400,7 @@ && isDeleteOnClose()) { /** * Registers a {@link Closeable} that should be closed when this socket is closed. - * + * * @param closeable The closeable. */ public final void addCloseable(Closeable closeable) { @@ -390,7 +409,7 @@ public final void addCloseable(Closeable closeable) { /** * Unregisters a previously registered {@link Closeable}. - * + * * @param closeable The closeable. */ public final void removeCloseable(Closeable closeable) { @@ -399,7 +418,7 @@ public final void removeCloseable(Closeable closeable) { /** * Checks whether everything is setup to support junixsocket sockets. - * + * * @return {@code true} if supported. */ public static boolean isSupported() { @@ -409,18 +428,25 @@ public static boolean isSupported() { @Override @SuppressFBWarnings("EI_EXPOSE_REP") public final @Nullable A getLocalSocketAddress() { - if (boundEndpoint == null) { - setBoundEndpoint(getAFImpl().getLocalSocketAddress()); + @Nullable + A ep = boundEndpoint0(); + if (ep == null) { + ep = getAFImpl().getLocalSocketAddress(); + setBoundEndpoint(ep); } + return ep; + } + + private synchronized @Nullable A boundEndpoint0() { return boundEndpoint; } /** * Checks if the local socket address returned by {@link #getLocalSocketAddress()} is still valid. - * + * * The address is no longer valid if the server socket has been closed, {@code null}, or another * server socket has been bound on that address. - * + * * @return {@code true} iff still valid. */ public boolean isLocalSocketAddressValid() { @@ -435,7 +461,7 @@ public boolean isLocalSocketAddressValid() { return addr.equals(getAFImpl().getLocalSocketAddress()); } - final void setBoundEndpoint(@Nullable A addr) { + final synchronized void setBoundEndpoint(@Nullable A addr) { this.boundEndpoint = addr; int port; if (addr == null) { @@ -448,10 +474,10 @@ final void setBoundEndpoint(@Nullable A addr) { @Override public final int getLocalPort() { - if (boundEndpoint == null) { + if (boundEndpoint0() == null) { setBoundEndpoint(getAFImpl().getLocalSocketAddress()); } - if (boundEndpoint == null) { + if (boundEndpoint0() == null) { return -1; } else { return getAFImpl().getLocalPort1(); @@ -460,10 +486,10 @@ public final int getLocalPort() { /** * Checks if this {@link AFServerSocket}'s file should be removed upon {@link #close()}. - * + * * Deletion is not guaranteed, especially when not supported (e.g., addresses in the abstract * namespace). - * + * * @return {@code true} if an attempt is made to delete the socket file upon {@link #close()}. */ public final boolean isDeleteOnClose() { @@ -473,10 +499,10 @@ public final boolean isDeleteOnClose() { /** * Enables/disables deleting this {@link AFServerSocket}'s file (or other resource type) upon * {@link #close()}. - * + * * Deletion is not guaranteed, especially when not supported (e.g., addresses in the abstract * namespace). - * + * * @param b Enabled if {@code true}. */ public final void setDeleteOnClose(boolean b) { @@ -486,6 +512,7 @@ public final void setDeleteOnClose(boolean b) { final AFSocketImpl getAFImpl() { if (created.compareAndSet(false, true)) { try { + getAFImpl().create(true); getSoTimeout(); // trigger create via java.net.Socket } catch (IOException e) { // ignore @@ -496,7 +523,7 @@ final AFSocketImpl getAFImpl() { @SuppressFBWarnings("EI_EXPOSE_REP") @Override - public AFServerSocketChannel getChannel() { + public AFServerSocketChannel getChannel() { return channel; } @@ -507,7 +534,7 @@ public final FileDescriptor getFileDescriptor() throws IOException { /** * Returns the address family supported by this implementation. - * + * * @return The family. */ protected final AFAddressFamily addressFamily() { @@ -517,9 +544,9 @@ protected final AFAddressFamily addressFamily() { /** * Sets the hook for any subsequent call to {@link #bind(SocketAddress)} and * {@link #bind(SocketAddress, int)} to be the given function. - * + * * The function can monitor calls or even alter the endpoint address. - * + * * @param hook The function that gets called for each {@code bind} call. * @return This instance. */ @@ -527,4 +554,117 @@ public final AFServerSocket bindHook(SocketAddressFilter hook) { this.bindFilter = hook; return this; } + + @Override + public InetAddress getInetAddress() { + if (!isBound()) { + return null; + } else { + return getAFImpl().getInetAddress(); + } + } + + @Override + public synchronized void setReceiveBufferSize(int size) throws SocketException { + if (size <= 0) { + throw new IllegalArgumentException("receive buffer size must be a positive number"); + } + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + getAFImpl().setOption(SocketOptions.SO_RCVBUF, size); + } + + @Override + public synchronized int getReceiveBufferSize() throws SocketException { + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + int result = 0; + Object o = getAFImpl().getOption(SocketOptions.SO_RCVBUF); + if (o instanceof Number) { + result = ((Number) o).intValue(); + } + return result; + } + + @Override + @SuppressWarnings("UnsynchronizedOverridesSynchronized" /* errorprone */) + public void setSoTimeout(int timeout) throws SocketException { + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + if (timeout < 0) { + throw new IllegalArgumentException("timeout < 0"); + } + getAFImpl().setOption(SocketOptions.SO_TIMEOUT, timeout); + } + + @Override + @SuppressWarnings("UnsynchronizedOverridesSynchronized" /* errorprone */) + public int getSoTimeout() throws IOException { + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + Object o = getAFImpl().getOption(SocketOptions.SO_TIMEOUT); + /* extra type safety */ + if (o instanceof Number) { + return ((Number) o).intValue(); + } else { + return 0; + } + } + + @Override + public void setReuseAddress(boolean on) throws SocketException { + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + getAFImpl().setOption(SocketOptions.SO_REUSEADDR, on); + } + + @Override + public boolean getReuseAddress() throws SocketException { + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + return ((Boolean) (getAFImpl().getOption(SocketOptions.SO_REUSEADDR))); + } + + @Override + public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) { + } + + @SuppressWarnings({"all", "MissingOverride" /* errorprone */}) + public T getOption(SocketOption name) throws IOException { + Objects.requireNonNull(name); + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + return getAFImpl().getOption(name); + } + + @SuppressWarnings({"all", "MissingOverride" /* errorprone */}) + public ServerSocket setOption(SocketOption name, T value) throws IOException { + Objects.requireNonNull(name); + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + getAFImpl().setOption(name, value); + return this; + } + + @SuppressWarnings("all") + public Set> supportedOptions() { + return getAFImpl().supportedOptions(); + } + + @Override + public void setShutdownOnClose(boolean enabled) { + getAFImpl().getCore().setShutdownOnClose(enabled); + } + + // NOTE: We shall re-implement all methods defined in ServerSocket that internally call getImpl() + // and call getAFImpl() here. This is not strictly necessary for environments where we can + // override "impl"; however it's the right thing to do. } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFServerSocketChannel.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFServerSocketChannel.java index 87dd4c5a9..2fafff00d 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFServerSocketChannel.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFServerSocketChannel.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,37 +17,42 @@ */ package org.newsclub.net.unix; +import static java.util.Objects.requireNonNull; + import java.io.FileDescriptor; import java.io.IOException; +import java.net.ProtocolFamily; import java.net.ServerSocket; import java.net.SocketAddress; import java.net.SocketOption; +import java.net.StandardProtocolFamily; import java.nio.channels.ServerSocketChannel; import java.nio.channels.spi.SelectorProvider; import java.util.Objects; import java.util.Set; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; /** * A selectable channel for stream-oriented listening sockets. - * + * * @param The concrete {@link AFSocketAddress} that is supported by this type. * @author Christian Kohlschütter */ public abstract class AFServerSocketChannel extends ServerSocketChannel - implements FileDescriptorAccess { + implements FileDescriptorAccess, AFSomeSocketChannel { private final @NonNull AFServerSocket afSocket; /** * Creates a new {@link AFServerSocketChannel} instance. - * + * * @param socket The corresponding {@link ServerSocket}. * @param sp The corresponding {@link SelectorProvider}. */ - @SuppressWarnings("null") + @SuppressWarnings("all") // null protected AFServerSocketChannel(AFServerSocket socket, AFSelectorProvider sp) { super(sp); this.afSocket = Objects.requireNonNull(socket); @@ -65,7 +70,7 @@ public T getOption(SocketOption name) throws IOException { if (optionId == null) { throw new UnsupportedOperationException("unsupported option"); } else { - return (T) afSocket.getAFImpl().getOption(optionId.intValue()); + return (T) afSocket.getAFImpl().getOption(optionId); } } @@ -79,7 +84,7 @@ public AFServerSocketChannel setOption(SocketOption name, T value) thr if (optionId == null) { throw new UnsupportedOperationException("unsupported option"); } else { - afSocket.getAFImpl().setOption(optionId.intValue(), value); + afSocket.getAFImpl().setOption(optionId, value); } return this; } @@ -105,21 +110,37 @@ public final AFServerSocket socket() { @Override public AFSocketChannel accept() throws IOException { - AFSocket socket = afSocket.accept1(false); - return socket == null ? null : socket.getChannel(); + boolean complete = false; + Exception exception = null; + try { + begin(); + AFSocket socket = afSocket.accept1(false); + complete = true; + return socket == null ? null : socket.getChannel(); + } catch (IOException e) { + throw InterruptibleChannelUtil.ioExceptionOrThrowRuntimeException( // NOPMD.PreserveStackTrace + (exception = InterruptibleChannelUtil.handleException(this, e))); + } finally { + InterruptibleChannelUtil.endInterruptable(this, this::end, complete, exception); + } + } + + @Override + public final @Nullable A getLocalAddress() { + return getLocalSocketAddress(); } @Override - public final AFSocketAddress getLocalAddress() throws IOException { + public final @Nullable A getLocalSocketAddress() { return afSocket.getLocalSocketAddress(); } /** * Checks if the local socket address returned by {@link #getLocalAddress()} is still valid. - * + * * The address is no longer valid if the server socket has been closed, {@code null}, or another * server socket has been bound on that address. - * + * * @return {@code true} iff still valid. */ public final boolean isLocalSocketAddressValid() { @@ -147,10 +168,10 @@ public final FileDescriptor getFileDescriptor() throws IOException { /** * Checks if this {@link AFServerSocketChannel}'s file should be removed upon {@link #close()}. - * + * * Deletion is not guaranteed, especially when not supported (e.g., addresses in the abstract * namespace). - * + * * @return {@code true} if an attempt is made to delete the socket file upon {@link #close()}. */ public final boolean isDeleteOnClose() { @@ -160,13 +181,44 @@ public final boolean isDeleteOnClose() { /** * Enables/disables deleting this {@link AFServerSocketChannel}'s file (or other resource type) * upon {@link #close()}. - * + * * Deletion is not guaranteed, especially when not supported (e.g., addresses in the abstract * namespace). - * + * * @param b Enabled if {@code true}. */ public final void setDeleteOnClose(boolean b) { socket().setDeleteOnClose(b); } + + @Override + public void setShutdownOnClose(boolean enabled) { + socket().setShutdownOnClose(enabled); + } + + /** + * Opens a server-socket channel. The {@code family} parameter specifies the {@link ProtocolFamily + * protocol family} of the channel's socket. + *

+ * If the {@link ProtocolFamily} is of an {@link AFProtocolFamily}, or {@code UNIX}, the + * corresponding junixsocket implementation is used. In all other cases, the call is delegated to + * {@link ServerSocketChannel#open()}. + * + * @param family The protocol family. + * @return The new {@link ServerSocketChannel}. + * @throws IOException on error. + */ + public static ServerSocketChannel open(ProtocolFamily family) throws IOException { + requireNonNull(family); + + if (family instanceof AFProtocolFamily) { + return ((AFProtocolFamily) family).openServerSocketChannel(); + } else if ("UNIX".equals(family.name())) { + return AFUNIXServerSocketChannel.open(); + } else if (family instanceof StandardProtocolFamily) { + return ServerSocketChannel.open(); + } else { + throw new UnsupportedOperationException("Protocol family not supported"); + } + } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFServerSocketConnector.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFServerSocketConnector.java new file mode 100644 index 000000000..094ce191b --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFServerSocketConnector.java @@ -0,0 +1,41 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.IOException; + +/** + * Some connector that is able to create {@link AFServerSocket}s bound to a given + * {@link AFSocketAddress}. + * + * @param The address type to bind to. + * @param The address type for the returned server socket (which should either be identical to + * {@code A} or {@link AFSocketAddress} to indicate that this could be any socket). + * @author Christian Kohlschütter + * @see AFSocketConnector + */ +public interface AFServerSocketConnector { + /** + * Creates an {@link AFServerSocket} bound to the given address. + * + * @param addr The address to bind to. + * @return The bound socket. + * @throws IOException on error. + */ + AFServerSocket bind(A addr) throws IOException; +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocket.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocket.java index 6df312e17..ed4da9b9d 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocket.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocket.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,10 +33,11 @@ /** * junixsocket's base implementation of a {@link Socket}. - * + * * @param The concrete {@link AFSocketAddress} that is supported by this type. * @author Christian Kohlschütter */ +@SuppressWarnings({"PMD.CouplingBetweenObjects", "PMD.CyclomaticComplexity"}) public abstract class AFSocket extends Socket implements AFSomeSocket, AFSocketExtensions { static final String PROP_LIBRARY_DISABLE_CAPABILITY_PREFIX = @@ -47,24 +48,27 @@ public abstract class AFSocket extends Socket impleme @SuppressWarnings("PMD.MutableStaticState") static String loadedLibrary; // set by NativeLibraryLoader - private static final int CAPABILITIES = initCapabilities(); + private static Integer capabilitiesValue = null; private final AFSocketImpl impl; private final AFSocketAddressFromHostname afh; private final Closeables closeables = new Closeables(); private final AtomicBoolean created = new AtomicBoolean(false); + + @SuppressWarnings("this-escape") private final AFSocketChannel channel = newChannel(); private @Nullable SocketAddressFilter connectFilter; /** * Creates a new {@link AFSocket} instance. - * + * * @param impl The corresponding {@link SocketImpl} class. * @param afh The conversion helper to get a socket address from an encoded hostname. * @throws SocketException on error. */ + @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") protected AFSocket(final AFSocketImpl impl, AFSocketAddressFromHostname afh) throws SocketException { super(impl); @@ -74,7 +78,7 @@ protected AFSocket(final AFSocketImpl impl, AFSocketAddressFromHostname af /** * Returns the {@link AFSocketAddress} type supported by this socket. - * + * * @return The supported {@link AFSocketAddress}. */ protected final Class socketAddressClass() { @@ -83,21 +87,21 @@ protected final Class socketAddressClass() { /** * Creates a new {@link AFSocketChannel} for this socket. - * + * * @return The new instance. */ protected abstract AFSocketChannel newChannel(); /** * The reference to the constructor of an {@link AFSocket} subclass. - * + * * @param The concrete {@link AFSocketAddress} that is supported by this type. */ @FunctionalInterface public interface Constructor { /** * Constructs a new {@link AFSocket} subclass instance. - * + * * @param fdObj The file descriptor. * @param factory The socket factory instance. * @return The instance. @@ -141,12 +145,12 @@ static AFSocket newInstance(Constructor constr /** * Creates a new, unbound {@link AFSocket}. - * + * * This "default" implementation is a bit "lenient" with respect to the specification. - * + * * In particular, we may ignore calls to {@link Socket#getTcpNoDelay()} and * {@link Socket#setTcpNoDelay(boolean)}. - * + * * @param The corresponding address type. * @param constr The implementation's {@link AFSocket} constructor * @param factory The corresponding socket factory, or {@code null}. @@ -166,7 +170,7 @@ protected static final AFSocket newInstance(Const /** * Creates a new {@link AFSocket} and connects it to the given {@link AFSocketAddress}. - * + * * @param The corresponding address type. * @param constr The implementation's {@link AFSocket} constructor * @param addr The address to connect to. @@ -183,7 +187,7 @@ protected static final AFSocket newInstance(Const /** * Creates a new {@link AFSocket} and connects it to the given {@link AFSocketAddress} using the * default implementation suited for that address type. - * + * * @param The corresponding address type. * @param addr The address to connect to. * @return A new, connected socket. @@ -198,7 +202,7 @@ public static final AFSocket connectTo(@NonNull A /** * Not supported, since it's not necessary for client sockets. - * + * * @see AFServerSocket */ @Override @@ -302,10 +306,10 @@ final String toStringSuffix() { /** * Returns true iff {@link AFSocket}s are supported by the current Java VM. - * + * * To support {@link AFSocket}s, a custom JNI library must be loaded that is supplied with * junixsocket. - * + * * @return {@code true} iff supported. */ public static boolean isSupported() { @@ -314,9 +318,9 @@ public static boolean isSupported() { /** * Checks if {@link AFSocket}s are supported by the current Java VM. - * + * * If not, an {@link UnsupportedOperationException} is thrown. - * + * * @throws UnsupportedOperationException if not supported. */ public static void ensureSupported() throws UnsupportedOperationException { @@ -325,13 +329,18 @@ public static void ensureSupported() throws UnsupportedOperationException { /** * Returns the version of the junixsocket library, as a string, for debugging purposes. - * + * * NOTE: Do not rely on the format of the version identifier, use socket capabilities instead. - * + * * @return String The version identifier, or {@code null} if it could not be determined. * @see #supports(AFSocketCapability) */ public static final String getVersion() { + String v = BuildProperties.getBuildProperties().get("git.build.version"); + if (v != null && !v.startsWith("$")) { + return v; + } + try { return NativeLibraryLoader.getJunixsocketVersion(); } catch (IOException e) { @@ -342,9 +351,9 @@ public static final String getVersion() { /** * Returns an identifier of the loaded native library, or {@code null} if the library hasn't been * loaded yet. - * + * * The identifier is useful mainly for debugging purposes. - * + * * @return The identifier of the loaded junixsocket-native library, or {@code null}. */ public static final String getLoadedLibrary() { @@ -371,6 +380,11 @@ public final void ensureAncillaryReceiveBufferSize(int minSize) { impl.ensureAncillaryReceiveBufferSize(minSize); } + private static boolean isCapDisabled(AFSocketCapability cap) { + return Boolean.parseBoolean(System.getProperty(PROP_LIBRARY_DISABLE_CAPABILITY_PREFIX + cap + .name(), "false")); + } + private static int initCapabilities() { if (!isSupported()) { return 0; @@ -392,41 +406,58 @@ private static int initCapabilities() { } } - private static boolean isCapDisabled(AFSocketCapability cap) { - return Boolean.valueOf(System.getProperty(PROP_LIBRARY_DISABLE_CAPABILITY_PREFIX + cap.name(), - "false")); + private static synchronized int capabilities() { + if (capabilitiesValue == null) { + capabilitiesValue = initCapabilities(); + } + return capabilitiesValue; } /** * Checks if the current environment (system platform, native library, etc.) supports a given * junixsocket capability. - * + * * Deprecated. Please use {@link #supports(AFSocketCapability)} instead. - * + * * NOTE: The result may or may not be cached from a previous call or from a check upon * initialization. - * + * * @param capability The capability. * @return true if supported. * @see #supports(AFSocketCapability) */ @Deprecated public static final boolean supports(AFUNIXSocketCapability capability) { - return (CAPABILITIES & capability.getBitmask()) != 0; + return (capabilities() & capability.getBitmask()) != 0; } /** * Checks if the current environment (system platform, native library, etc.) supports a given * junixsocket capability. - * + * * NOTE: The result may or may not be cached from a previous call or from a check upon * initialization. - * + * * @param capability The capability. * @return true if supported. */ public static final boolean supports(AFSocketCapability capability) { - return (CAPABILITIES & capability.getBitmask()) != 0; + return (capabilities() & capability.getBitmask()) != 0; + } + + /** + * Checks if the current environment (system platform, native library, etc.) supports "unsafe" + * operations (as controlled via the {@link AFSocketCapability#CAPABILITY_UNSAFE} capability). + * + * If supported, the method returns normally. If not supported, an {@link IOException} is thrown. + * + * @throws IOException if "unsafe" operations are not supported. + * @see Unsafe + */ + public static final void ensureUnsafeSupported() throws IOException { + if (!AFSocket.supports(AFSocketCapability.CAPABILITY_UNSAFE)) { + throw new IOException("Unsafe operations are not supported in this environment"); + } } @Override @@ -442,7 +473,7 @@ public final synchronized void close() throws IOException { /** * Registers a {@link Closeable} that should be closed when this socket is closed. - * + * * @param closeable The closeable. */ public final void addCloseable(Closeable closeable) { @@ -451,7 +482,7 @@ public final void addCloseable(Closeable closeable) { /** * Unregisters a previously registered {@link Closeable}. - * + * * @param closeable The closeable. */ public final void removeCloseable(Closeable closeable) { @@ -514,7 +545,7 @@ public final AFOutputStream getOutputStream() throws IOException { /** * Returns the internal helper instance for address-specific extensions. - * + * * @return The helper instance. * @throws UnsupportedOperationException if such extensions are not supported for this address * type. @@ -526,7 +557,7 @@ protected final AFSocketImplExtensions getImplExtensions() { /** * Forces the address to be used for any subsequent call to {@link #connect(SocketAddress)} to be * the given one, regardless of what'll be passed there. - * + * * @param endpoint The forced endpoint address. * @return This instance. */ @@ -539,9 +570,9 @@ public final AFSocket forceConnectAddress(SocketAddress endpoint) { /** * Sets the hook for any subsequent call to {@link #connect(SocketAddress)} or * {@link #connect(SocketAddress, int)} to be the given function. - * + * * The function can monitor events or even alter the target address. - * + * * @param hook The function that gets called for each connect call. * @return This instance. */ @@ -552,10 +583,10 @@ public final AFSocket connectHook(SocketAddressFilter hook) { /** * Probes the status of the socket connection. - * + * * This usually involves checking for {@link #isConnected()}, and if assumed connected, also * sending a zero-length message to the remote. - * + * * @return {@code true} if the connection is known to be closed, {@code false} if the connection * is open/not closed or the condition is unknown. * @throws IOException on an unexpected error. @@ -580,4 +611,18 @@ public boolean checkConnectionClosed() throws IOException { } } } + + /** + * Checks if we're running on Android (as far as junixsocket is concerned). + * + * @return {@code true} if running on Android. + */ + public static boolean isRunningOnAndroid() { + return NativeLibraryLoader.isAndroid(); + } + + @Override + public void setShutdownOnClose(boolean enabled) { + getAFImpl().getCore().setShutdownOnClose(enabled); + } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketAddress.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketAddress.java index 13ff3a541..989a78176 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketAddress.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketAddress.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,13 @@ */ package org.newsclub.net.unix; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -29,6 +32,7 @@ import java.net.SocketException; import java.net.URI; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -36,14 +40,21 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import org.newsclub.net.unix.pool.ObjectPool; +import org.newsclub.net.unix.pool.ObjectPool.Lease; + +import com.google.errorprone.annotations.Immutable; +import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; /** * Some {@link SocketAddress} that is supported by junixsocket, such as {@link AFUNIXSocketAddress}. - * + * * @author Christian Kohlschütter */ +@Immutable +@SuppressWarnings({"PMD.CouplingBetweenObjects", "PMD.CyclomaticComplexity"}) public abstract class AFSocketAddress extends InetSocketAddress { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; // do not change! /** * Just a marker for "don't actually bind" (checked with "=="). Used in combination with a @@ -65,102 +76,256 @@ public abstract class AFSocketAddress extends InetSocketAddress { private static final Map, Map>> ADDRESS_CACHE = new HashMap<>(); - static final ThreadLocal SOCKETADDRESS_BUFFER_TL = new ThreadLocal() { + static final ObjectPool SOCKETADDRESS_BUFFER_TL = ObjectPool.newThreadLocalPool( + () -> { + return AFSocketAddress.newSockAddrDirectBuffer(SOCKADDR_MAX_LEN); + }, (o) -> { + o.clear(); + return true; + }); - @Override - protected ByteBuffer initialValue() { - return AFSocketAddress.newSockAddrDirectBuffer(SOCKADDR_MAX_LEN); - } - }; + private static final boolean USE_DESERIALIZATION_FOR_INIT; + + static { + String v = System.getProperty("org.newsclub.net.unix.AFSocketAddress.deserialize", ""); + USE_DESERIALIZATION_FOR_INIT = v.isEmpty() ? NativeLibraryLoader.isAndroid() : Boolean + .parseBoolean(v); + } /** * Some byte-level representation of this address, which can only be converted to a native * representation in combination with the domain ID. */ - private final byte[] bytes; + @SuppressFBWarnings("JCIP_FIELD_ISNT_FINAL_IN_IMMUTABLE_CLASS") + private byte[] bytes; /** * An {@link InetAddress}-wrapped representation of this address. Only created upon demand. */ - private InetAddress inetAddress = null; + @SuppressFBWarnings("JCIP_FIELD_ISNT_FINAL_IN_IMMUTABLE_CLASS") // only modified during + // construction/deserialization + private InetAddress inetAddress = null; // derived from bytes /** * The system-native representation of this address, or {@code null}. */ - private final ByteBuffer nativeAddress; + @SuppressWarnings("PMD.ImmutableField") + private transient ByteBuffer nativeAddress; /** * The address family. */ - private final AFAddressFamily addressFamily; + private transient AFAddressFamily addressFamily; /** * Creates a new socket address. - * + * * @param port The port. * @param socketAddress The socket address in junixsocket-specific byte-array representation. * @param nativeAddress The socket address in system-native representation. * @param af The address family. * @throws SocketException on error. */ - protected AFSocketAddress(int port, final byte[] socketAddress, ByteBuffer nativeAddress, + @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") + protected AFSocketAddress(int port, final byte[] socketAddress, Lease nativeAddress, AFAddressFamily af) throws SocketException { /* * Initializing the superclass with an unresolved hostname helps us pass the #equals and * #hashCode checks, which unfortunately are declared final in InetSocketAddress. - * + * * Using a resolved address (with the address bit initialized) would be ideal, but resolved * addresses can only be IPv4 or IPv6 (at least as of Java 16 and earlier). */ - super(AFInetAddress.createUnresolvedHostname(socketAddress, af), 0); - this.nativeAddress = nativeAddress == null ? null : (ByteBuffer) nativeAddress.duplicate() - .rewind(); + super(AFInetAddress.createUnresolvedHostname(socketAddress, af), port >= 0 && port <= 0xffff + ? port : 0); + initAFSocketAddress(this, port, socketAddress, nativeAddress, af); + } + + /** + * Only for {@link SentinelSocketAddress}. + * + * @param clazz The {@link SentinelSocketAddress} class. + * @param port A sentinel port number. + */ + @SuppressWarnings("PMD.UnusedFormalParameter") + AFSocketAddress(Class clazz, int port) { + super(InetAddress.getLoopbackAddress(), port); + this.nativeAddress = null; + this.bytes = new byte[0]; + this.addressFamily = null; + } + + @SuppressWarnings({"cast", "this-escape"}) + private static void initAFSocketAddress(AFSocketAddress addr, int port, + final byte[] socketAddress, Lease nativeAddress, AFAddressFamily af) + throws SocketException { + if (socketAddress.length == 0) { + throw new SocketException("Illegal address length: " + socketAddress.length); + } + + addr.nativeAddress = nativeAddress == null ? null : (ByteBuffer) (Object) nativeAddress.get() + .duplicate().rewind(); if (port < -1) { throw new IllegalArgumentException("port out of range"); - } - if (port > 0) { + } else if (port > 0xffff) { if (!NativeUnixSocket.isLoaded()) { throw (SocketException) new SocketException( "Cannot set SocketAddress port - junixsocket JNI library is not available").initCause( NativeUnixSocket.unsupportedException()); } - NativeUnixSocket.setPort1(this, port); + NativeUnixSocket.setPort1(addr, port); } - if (socketAddress.length == 0) { - throw new SocketException("Illegal address length: " + socketAddress.length); + + addr.bytes = socketAddress.clone(); + addr.addressFamily = af; + } + + /** + * Returns a new {@link AFSocketAddress} instance via deserialization. This is a trick to + * workaround certain environments that do not allow the construction of {@link InetSocketAddress} + * instances without trying DNS resolution. + * + * @param The subclass (must be a direct subclass of {@link AFSocketAddress}). + * @param port The port to use. + * @param socketAddress The junixsocket representation of the socket address. + * @param nativeAddress The system-native representation of the socket address, or {@code null}. + * @param af The address family, corresponding to the subclass + * @param constructor The constructor to use as fallback + * @return The new instance. + * @throws SocketException on error. + */ + protected static A newDeserializedAFSocketAddress(int port, + final byte[] socketAddress, Lease nativeAddress, AFAddressFamily af, + AFSocketAddressConstructor constructor) throws SocketException { + String hostname = AFInetAddress.createUnresolvedHostname(socketAddress, af); + if (hostname == null || hostname.isEmpty()) { + return constructor.newAFSocketAddress(port, socketAddress, nativeAddress); } + try (ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(AFSocketAddress + .craftSerializedObject(af.getSocketAddressClass(), hostname, (port >= 0 && port <= 0xffff + ? port : 0))))) { + @SuppressWarnings("unchecked") + A addr = (A) oin.readObject(); + initAFSocketAddress(addr, port, socketAddress, nativeAddress, af); + return addr; + } catch (SocketException e) { + throw e; + } catch (ClassNotFoundException | IOException e) { + throw (SocketException) new SocketException("Unexpected deserialization problem").initCause( + e); + } + } + + /** + * Creates a byte-representation of a serialized {@link AFSocketAddress} instance, overriding + * hostname and port, which allows bypassing DNS resolution. + * + * @param className The actual subclass. + * @param hostname The hostname to use (must not be empty or null). + * @param port The port to use. + * @return The byte representation. + */ + private static byte[] craftSerializedObject(Class className, + String hostname, int port) { + ByteBuffer bb = ByteBuffer.allocate(768); + bb.putShort((short) 0xaced); // STREAM_MAGIC + bb.putShort((short) 5); // STREAM_VERSION + bb.put((byte) 0x73); // TC_OBJECT + bb.put((byte) 0x72); // TC_CLASSDESC + + putShortLengthUtf8(bb, className.getName()); + bb.putLong(1); // serialVersionUID of subclass (expected to be 1) + bb.putInt(0x02000078); + bb.put((byte) 0x72); + + putShortLengthUtf8(bb, AFSocketAddress.class.getName()); + bb.putLong(serialVersionUID); // serialVersionUID of AFSocketAddress + bb.putInt(0x0300025B); + putShortLengthUtf8(bb, "bytes"); - this.bytes = socketAddress.clone(); - this.addressFamily = af; + bb.putInt(0x7400025B); + bb.putShort((short) 0x424C); + + putShortLengthUtf8(bb, "inetAddress"); + bb.put((byte) 0x74); + + putShortLengthEncodedClassName(bb, InetAddress.class); + + bb.putShort((short) 0x7872); + putShortLengthUtf8(bb, InetSocketAddress.class.getName()); + bb.putLong(5076001401234631237L); // NOPMD InetSocketAddress serialVersionUID + + bb.putInt(0x03000349); + putShortLengthUtf8(bb, "port"); + + bb.put((byte) 0x4C); + putShortLengthUtf8(bb, "addr"); + + bb.putInt(0x71007E00); + bb.putShort((short) 0x034C); + putShortLengthUtf8(bb, "hostname"); + bb.put((byte) 0x74); + + putShortLengthEncodedClassName(bb, String.class); + + bb.putShort((short) 0x7872); + putShortLengthUtf8(bb, SocketAddress.class.getName()); + bb.putLong(5215720748342549866L); // NOPMD SocketAddress serialVersionUID + + bb.putInt(0x02000078); + bb.put((byte) 0x70); + bb.putInt(port); + + bb.putShort((short) 0x7074); + putShortLengthUtf8(bb, hostname); + + bb.putInt(0x78707077); + bb.put((byte) 0x0B); + + putShortLengthUtf8(bb, "undefined"); + + bb.put((byte) 0x78); // TC_ENDBLOCKDATA + bb.flip(); + + byte[] buf = new byte[bb.remaining()]; + bb.get(buf); + return buf; + } + + private static void putShortLengthEncodedClassName(ByteBuffer bb, Class klazz) { + putShortLengthUtf8(bb, "L" + klazz.getName().replace('.', '/') + ";"); + } + + private static void putShortLengthUtf8(ByteBuffer bb, String s) { + byte[] utf8 = s.getBytes(StandardCharsets.UTF_8); + bb.putShort((short) utf8.length); + bb.put(utf8); } /** - * Only for {@link SentinelSocketAddress}. - * - * @param clazz The {@link SentinelSocketAddress} class. - * @param port A sentinel port number. + * Checks if {@link AFSocketAddress} instantiation should be performed via deserialization. + * + * @return {@code true} if so. + * @see #newDeserializedAFSocketAddress(int, byte[], Lease, AFAddressFamily, + * AFSocketAddressConstructor) */ - @SuppressWarnings("PMD.UnusedFormalParameter") - protected AFSocketAddress(Class clazz, int port) { - super(InetAddress.getLoopbackAddress(), port); - this.nativeAddress = null; - this.bytes = new byte[0]; - this.addressFamily = null; + protected static boolean isUseDeserializationForInit() { + return USE_DESERIALIZATION_FOR_INIT; } /** * Checks if the address can be resolved to a {@link File}. - * + * * @return {@code true} if the address has a filename. */ public abstract boolean hasFilename(); /** * Returns the {@link File} corresponding with this address, if possible. - * + * * A {@link FileNotFoundException} is thrown if there is no filename associated with the address, * which applies to addresses in the abstract namespace, for example. - * + * * @return The filename. * @throws FileNotFoundException if the address is not associated with a filename. */ @@ -168,7 +333,7 @@ protected AFSocketAddress(Class clazz, int port) { /** * Returns the corresponding {@link AFAddressFamily}. - * + * * @return The address family instance. */ public final AFAddressFamily getAddressFamily() { @@ -177,7 +342,7 @@ public final AFAddressFamily getAddressFamily() { /** * Wraps the socket name/peer name of a file descriptor as an {@link InetAddress}. - * + * * @param fdesc The file descriptor. * @param peerName If {@code true}, the remote peer name (instead of the local name) is retrieved. * @param af The address family. @@ -188,7 +353,7 @@ protected static final InetAddress getInetAddress(FileDescriptor fdesc, boolean if (!fdesc.valid()) { return null; } - byte[] addr = NativeUnixSocket.sockname(af.getDomain(), fdesc, peerName); // FIXME + byte[] addr = NativeUnixSocket.sockname(af.getDomain(), fdesc, peerName); if (addr == null) { return null; } @@ -197,20 +362,21 @@ protected static final InetAddress getInetAddress(FileDescriptor fdesc, boolean /** * Gets the socket name/peer name of a file descriptor as an {@link AFSocketAddress}. - * + * * @param The corresponding address type. * @param fdesc The file descriptor. - * @param peerName If {@code true}, the remote peer name (instead of the local name) is retrieved. + * @param requestPeerName If {@code true}, the remote peer name (instead of the local name) is + * retrieved. * @param port The port. * @param af The address family. * @return The {@link InetAddress}. */ protected static final @Nullable A getSocketAddress( - FileDescriptor fdesc, boolean peerName, int port, AFAddressFamily af) { + FileDescriptor fdesc, boolean requestPeerName, int port, AFAddressFamily af) { if (!fdesc.valid()) { return null; } - byte[] addr = NativeUnixSocket.sockname(af.getDomain(), fdesc, peerName); // FIXME + byte[] addr = NativeUnixSocket.sockname(af.getDomain(), fdesc, requestPeerName); if (addr == null) { return null; } @@ -245,6 +411,7 @@ static final AFSocketAddress preprocessSocketAddress( } } } + endpoint = mapOrFail(endpoint, supportedAddressClass); } Objects.requireNonNull(endpoint); @@ -259,7 +426,7 @@ static final AFSocketAddress preprocessSocketAddress( /** * Returns the (non-native) byte-level representation of this address. - * + * * @return The byte array. */ protected final byte[] getBytes() { @@ -269,12 +436,12 @@ protected final byte[] getBytes() { /** * Returns a "special" {@link InetAddress} that contains information about this * {@link AFSocketAddress}. - * + * * IMPORTANT: This {@link InetAddress} does not properly compare (using * {@link InetAddress#equals(Object)} and {@link InetAddress#hashCode()}). It should be used * exclusively to circumvent existing APIs like {@link DatagramSocket} that only accept/return * {@link InetAddress} and not arbitrary {@link SocketAddress} types. - * + * * @return The "special" {@link InetAddress}. */ public final InetAddress wrapAddress() { @@ -283,7 +450,7 @@ public final InetAddress wrapAddress() { /** * A reference to the constructor of an AFSocketAddress subclass. - * + * * @param The actual subclass. * @author Christian Kohlschütter */ @@ -291,7 +458,7 @@ public final InetAddress wrapAddress() { protected interface AFSocketAddressConstructor { /** * Constructs a new AFSocketAddress instance. - * + * * @param port The port. * @param socketAddress The socket address in junixsocket-specific byte-array representation. * @param nativeAddress The socket address in system-native representation. @@ -299,14 +466,14 @@ protected interface AFSocketAddressConstructor { * @throws SocketException on error. */ @NonNull - T newAFSocketAddress(int port, byte[] socketAddress, ByteBuffer nativeAddress) + T newAFSocketAddress(int port, byte[] socketAddress, Lease nativeAddress) throws SocketException; } /** * Resolves a junixsocket-specific byte-array representation of an {@link AFSocketAddress} to an * actual {@link AFSocketAddress} instance, possibly reusing a cached instance. - * + * * @param The concrete {@link AFSocketAddress} that is supported by this type. * @param socketAddress The socket address in junixsocket-specific byte-array representation. * @param port The port. @@ -317,8 +484,6 @@ T newAFSocketAddress(int port, byte[] socketAddress, ByteBuffer nativeAddress) @SuppressWarnings({"unchecked", "null"}) protected static final A resolveAddress(final byte[] socketAddress, int port, AFAddressFamily af) throws SocketException { - A instance; - if (socketAddress.length == 0) { throw new SocketException("Address cannot be empty"); } @@ -327,88 +492,96 @@ protected static final A resolveAddress(final byte[] port = 0; } - ByteBuffer direct = SOCKETADDRESS_BUFFER_TL.get(); - int limit = NativeUnixSocket.isLoaded() ? NativeUnixSocket.bytesToSockAddr(af.getDomain(), - direct, socketAddress) : -1; - if (limit == -1) { - // not supported, but we can still create an address - return af.getAddressConstructor().newAFSocketAddress(port, socketAddress, null); - } else if (limit > SOCKADDR_MAX_LEN) { - throw new IllegalStateException("Unexpected address length"); - } - direct.rewind(); - direct.limit(limit); - - synchronized (AFSocketAddress.class) { - Map map; - Map> mapPorts = ADDRESS_CACHE.get(af); - if (mapPorts == null) { - instance = null; - mapPorts = new HashMap<>(); - map = new HashMap<>(); - mapPorts.put(port, map); - ADDRESS_CACHE.put(af, mapPorts); - } else { - map = mapPorts.get(port); - if (map == null) { + try (Lease lease = SOCKETADDRESS_BUFFER_TL.take()) { + ByteBuffer direct = lease.get(); + int limit = NativeUnixSocket.isLoaded() ? NativeUnixSocket.bytesToSockAddr(af.getDomain(), + direct, socketAddress) : -1; + if (limit == -1) { + // not supported, but we can still create an address + return af.getAddressConstructor().newAFSocketAddress(port, socketAddress, null); + } else if (limit > SOCKADDR_MAX_LEN) { + throw new IllegalStateException("Unexpected address length"); + } + direct.rewind(); + direct.limit(limit); + + A instance; + synchronized (AFSocketAddress.class) { + Map map; + Map> mapPorts = ADDRESS_CACHE.get(af); + if (mapPorts == null) { instance = null; + mapPorts = new HashMap<>(); map = new HashMap<>(); mapPorts.put(port, map); + ADDRESS_CACHE.put(af, mapPorts); } else { - instance = (A) map.get(direct); + map = mapPorts.get(port); + if (map == null) { + instance = null; + map = new HashMap<>(); + mapPorts.put(port, map); + } else { + instance = (A) map.get(direct); + } } - } - if (instance == null) { - ByteBuffer key = newSockAddrKeyBuffer(limit); - key.put(direct); - key = key.asReadOnlyBuffer(); + if (instance == null) { + ByteBuffer key = newSockAddrKeyBuffer(limit); + key.put(direct); + key = key.asReadOnlyBuffer(); - instance = af.getAddressConstructor().newAFSocketAddress(port, socketAddress, key); + instance = af.getAddressConstructor().newAFSocketAddress(port, socketAddress, ObjectPool + .unpooledLease(key)); - map.put(key, instance); + map.put(key, instance); + } } + return instance; } - - return instance; } @SuppressWarnings("null") static final A ofInternal(ByteBuffer socketAddressBuffer, AFAddressFamily af) throws SocketException { synchronized (AFSocketAddress.class) { + socketAddressBuffer.rewind(); + Map> mapPorts = ADDRESS_CACHE.get(af); if (mapPorts != null) { Map map = mapPorts.get(0); // FIXME get port, something like // sockAddrToPort if (map != null) { @SuppressWarnings("unchecked") - A address = (A) map.get(socketAddressBuffer.rewind()); + A address = (A) map.get(socketAddressBuffer); if (address != null) { return address; } } } - if (!socketAddressBuffer.isDirect()) { - ByteBuffer buf = getNativeAddressDirectBuffer(Math.min(socketAddressBuffer.limit(), - SOCKADDR_MAX_LEN)); - buf.put(socketAddressBuffer); - socketAddressBuffer = buf; - } - byte[] sockAddrToBytes = NativeUnixSocket.sockAddrToBytes(af.getDomain(), - socketAddressBuffer); - if (sockAddrToBytes == null) { - return null; - } else { - return AFSocketAddress.resolveAddress(sockAddrToBytes, 0, af); + try (Lease leasedBuffer = socketAddressBuffer.isDirect() ? null + : getNativeAddressDirectBuffer(Math.min(socketAddressBuffer.limit(), SOCKADDR_MAX_LEN))) { + if (leasedBuffer != null) { + ByteBuffer buf = leasedBuffer.get(); + buf.put(socketAddressBuffer); + socketAddressBuffer = buf; + } + + byte[] sockAddrToBytes = NativeUnixSocket.sockAddrToBytes(af.getDomain(), + socketAddressBuffer); + if (sockAddrToBytes == null) { + return null; + } else { + return AFSocketAddress.resolveAddress(sockAddrToBytes, 0, af); + } } } } /** * Wraps an address as an {@link InetAddress}. - * + * * @param af The address family. * @return The {@link InetAddress}. */ @@ -421,25 +594,27 @@ protected final synchronized InetAddress getInetAddress(AFAddressFamily af) { /** * Wraps this address as an {@link InetAddress}. - * + * * @return The {@link InetAddress}. */ protected final InetAddress getInetAddress() { return getInetAddress(getAddressFamily()); } - static final ByteBuffer newSockAddrDirectBuffer(int length) { + @SuppressWarnings("null") + static final @NonNull ByteBuffer newSockAddrDirectBuffer(int length) { return ByteBuffer.allocateDirect(length); } - static final ByteBuffer newSockAddrKeyBuffer(int length) { + @SuppressWarnings("null") + static final @NonNull ByteBuffer newSockAddrKeyBuffer(int length) { return ByteBuffer.allocate(length); } /** * Returns an {@link AFSocketAddress} given a special {@link InetAddress} that encodes the byte * sequence of an AF_UNIX etc. socket address, like those returned by {@link #wrapAddress()}. - * + * * @param The corresponding address type. * @param address The "special" {@link InetAddress}. * @param port The port (use 0 for "none"). @@ -460,7 +635,7 @@ protected static final A unwrap(InetAddress address, * Returns an {@link AFSocketAddress} given a special {@link InetAddress} hostname that encodes * the byte sequence of an AF_UNIX etc. socket address, like those returned by * {@link #wrapAddress()}. - * + * * @param The corresponding address type. * @param hostname The "special" hostname, as provided by {@link InetAddress#getHostName()}. * @param port The port (use 0 for "none"). @@ -479,16 +654,18 @@ protected static final A unwrap(String hostname, int static final int unwrapAddressDirectBufferInternal(ByteBuffer socketAddressBuffer, SocketAddress address) throws SocketException { - Objects.requireNonNull(address); - // FIXME: add support for UnixDomainSocketAddress - if (!(address instanceof AFSocketAddress)) { - throw new SocketException("Unsupported address"); + if (!NativeUnixSocket.isLoaded()) { + throw new SocketException("Unsupported operation; junixsocket native library is not loaded"); } + Objects.requireNonNull(address); - byte[] addr = ((AFSocketAddress) address).getBytes(); - int domain = ((AFSocketAddress) address).getAddressFamily().getDomain(); - int len = NativeUnixSocket.isLoaded() ? NativeUnixSocket.bytesToSockAddr(domain, - socketAddressBuffer, addr) : -1; + address = AFSocketAddress.mapOrFail(address, AFSocketAddress.class); + AFSocketAddress socketAddress = (AFSocketAddress) address; + + byte[] addr = socketAddress.getBytes(); + int domain = socketAddress.getAddressFamily().getDomain(); + + int len = NativeUnixSocket.bytesToSockAddr(domain, socketAddressBuffer, addr); if (len == -1) { throw new SocketException("Unsupported domain"); } @@ -498,27 +675,36 @@ static final int unwrapAddressDirectBufferInternal(ByteBuffer socketAddressBuffe /** * Returns a thread-local direct ByteBuffer containing the native socket address representation of * this {@link AFSocketAddress}. - * + * * @return The direct {@link ByteBuffer}. */ - final ByteBuffer getNativeAddressDirectBuffer() { - ByteBuffer direct = getNativeAddressDirectBuffer(nativeAddress.limit()); - nativeAddress.position(0); - direct.put(nativeAddress); + final Lease getNativeAddressDirectBuffer() throws SocketException { + ByteBuffer address = nativeAddress; + if (address == null) { + throw (SocketException) new SocketException("Cannot access native address").initCause( + NativeUnixSocket.unsupportedException()); + } + address = address.duplicate(); - return direct; + Lease lease = getNativeAddressDirectBuffer(address.limit()); + ByteBuffer direct = lease.get(); + address.position(0); + direct.put(address); + + return lease; } - static final ByteBuffer getNativeAddressDirectBuffer(int limit) { - ByteBuffer direct = SOCKETADDRESS_BUFFER_TL.get(); + static final Lease getNativeAddressDirectBuffer(int limit) { + Lease lease = SOCKETADDRESS_BUFFER_TL.take(); + ByteBuffer direct = lease.get(); direct.position(0); direct.limit(limit); - return direct; + return lease; } /** * Checks if the given address is supported by this address family. - * + * * @param addr The address. * @param af The address family. * @return {@code true} if supported. @@ -529,22 +715,23 @@ protected static final boolean isSupportedAddress(InetAddress addr, AFAddressFam /** * Writes the native (system-level) representation of this address to the given buffer. - * + * * The position of the target buffer will be at the end (i.e., after) the written data. - * + * * @param buf The target buffer. * @throws IOException on error. */ public final void writeNativeAddressTo(ByteBuffer buf) throws IOException { if (nativeAddress == null) { - throw new IOException("Cannot access native address"); + throw (SocketException) new SocketException("Cannot access native address").initCause( + NativeUnixSocket.unsupportedException()); } buf.put(nativeAddress); } /** * Creates a new socket connected to this address. - * + * * @return The socket instance. * @throws IOException on error. */ @@ -556,7 +743,7 @@ public AFSocket newConnectedSocket() throws IOException { /** * Creates a new server socket bound to this address. - * + * * @return The server socket instance. * @throws IOException on error. */ @@ -570,7 +757,7 @@ public AFServerSocket newBoundServerSocket() throws IOException { * Creates a new server socket force-bound to this address (i.e., any additional call to * {@link ServerSocket#bind(SocketAddress)} will ignore the passed address and use this one * instead. - * + * * @return The server socket instance. * @throws IOException on error. */ @@ -582,10 +769,10 @@ public AFServerSocket newForceBoundServerSocket() throws IOException { /** * Tries to parse the given URI and return a corresponding {@link AFSocketAddress} for it. - * + * * NOTE: Only certain URI schemes are supported, such as {@code unix://} (for * {@link AFUNIXSocketAddress}) and {@code tipc://} for {@link AFTIPCSocketAddress}. - * + * * @param u The URI. * @return The address. * @throws SocketException on error. @@ -598,10 +785,10 @@ public static AFSocketAddress of(URI u) throws SocketException { /** * Tries to parse the given URI and return a corresponding {@link AFSocketAddress} for it. - * + * * NOTE: Only certain URI schemes are supported, such as {@code unix://} (for * {@link AFUNIXSocketAddress}) and {@code tipc://} for {@link AFTIPCSocketAddress}. - * + * * @param u The URI. * @param overridePort The port to forcibly use, or {@code -1} for "don't override". * @return The address. @@ -619,7 +806,7 @@ public static AFSocketAddress of(URI u, int overridePort) throws SocketException /** * Tries to create a URI based on this {@link AFSocketAddress}. - * + * * @param scheme The target scheme. * @param template An optional template to reuse certain parameters (e.g., the "path" component * for an {@code http} request), or {@code null}. @@ -634,14 +821,15 @@ public URI toURI(String scheme, URI template) throws IOException { * Returns a address string that can be used with {@code socat}'s {@code SOCKET-CONNECT}, * {@code SOCKET-LISTEN}, {@code SOCKET-DATAGRAM}, etc., address types, or {@code null} if the * address type is not natively supported by this platform. - * + * * This call is mostly suited for debugging purposes. The resulting string is specific to the - * platform the code is executed on, and thus may be different among platforms (or {@code null}). - * + * platform the code is executed on, and thus may be different among platforms. + * * @param socketType The socket type, or {@code null} to omit from string. * @param socketProtocol The socket protocol, or {@code null} to omit from string. - * @return The string (such as 1:0:x2f746d702f796f), or {@code null} if unable to retrieve. - * @throws IOException on error. + * @return The string (such as 1:0:x2f746d702f796f). + * @throws IOException on error (a {@link SocketException} is thrown if the native address cannot + * be accessed). */ public @Nullable @SuppressWarnings("PMD.NPathComplexity") String toSocatAddressString( AFSocketType socketType, AFSocketProtocol socketProtocol) throws IOException { @@ -650,10 +838,11 @@ public URI toURI(String scheme, URI template) throws IOException { return null; } if (nativeAddress == null) { - return null; + throw (SocketException) new SocketException("Cannot access native address").initCause( + NativeUnixSocket.unsupportedException()); } if (socketProtocol != null && socketProtocol.getId() != 0) { - throw new IOException("Protocol not (yet) supported"); // FIXME + throw new IOException("Protocol not (yet) supported"); // FIXME support additional protocols } int family = (nativeAddress.get(SOCKADDR_NATIVE_FAMILY_OFFSET) & 0xFF); @@ -682,17 +871,193 @@ public URI toURI(String scheme, URI template) throws IOException { /** * Checks if the given address could cover another address. - * + * * By default, this is only true if both addresses are regarded equal using * {@link #equals(Object)}. - * + * * However, implementations may support "wildcard" addresses, and this method would compare a * wildcard address against some non-wildcard address, for example. - * + * * @param other The other address that could be covered by this address. * @return {@code true} if the other address could be covered. */ public boolean covers(AFSocketAddress other) { return this.equals(other); } + + /** + * Custom serialization: Reference {@link AFAddressFamily} instance by identifier string. + * + * @param in The {@link ObjectInputStream}. + * @throws ClassNotFoundException on error. + * @throws IOException on error. + */ + @SuppressFBWarnings("MC_OVERRIDABLE_METHOD_CALL_IN_READ_OBJECT") // https://github.com/spotbugs/spotbugs/issues/2957 + private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException { + in.defaultReadObject(); + + String af = in.readUTF(); + if ("undefined".equals(af)) { + this.addressFamily = null; + } else { + this.addressFamily = Objects.requireNonNull(AFAddressFamily.getAddressFamily(af), + "address family"); + } + } + + /** + * Custom serialization: Reference {@link AFAddressFamily} instance by identifier string. + * + * @param out The {@link ObjectOutputStream}. + * @throws IOException on error. + */ + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeUTF(addressFamily == null ? "undefined" : addressFamily.getJuxString()); + } + + /** + * Returns a string representation of the argument as an unsigned decimal value. + *

+ * Works like {@link Integer#toUnsignedString(int)}; added to allow execution on Java 1.7. + * + * @param i The value. + * @return The string. + */ + static String toUnsignedString(int i) { + return Long.toString(toUnsignedLong(i)); + } + + /** + * Returns a string representation of the first argument as an unsigned integer value in the radix + * specified by the second argument; added to allow execution on Java 1.7. + * + * @param i The value. + * @param radix The radix. + * @return The string. + */ + static String toUnsignedString(int i, int radix) { + return Long.toUnsignedString(toUnsignedLong(i), radix); + } + + private static long toUnsignedLong(long x) { + return x & 0xffffffffL; + } + + /** + * Parses the string argument as an unsigned integer in the radix specified by the second + * argument. Works like {@link Integer#parseUnsignedInt(String, int)}; added to allow execution on + * Java 1.7. + * + * @param s The string. + * @param radix The radix. + * @return The integer. + * @throws NumberFormatException on parse error. + */ + protected static int parseUnsignedInt(String s, int radix) throws NumberFormatException { + if (s == null || s.isEmpty()) { + throw new NumberFormatException("Cannot parse null or empty string"); + } + + int len = s.length(); + if (s.startsWith("-")) { + throw new NumberFormatException("Illegal leading minus sign on unsigned string " + s); + } + + if (len <= 5 || (radix == 10 && len <= 9)) { + return Integer.parseInt(s, radix); + } else { + long ell = Long.parseLong(s, radix); + if ((ell & 0xffff_ffff_0000_0000L) == 0) { + return (int) ell; + } else { + throw new NumberFormatException("String value exceeds " + "range of unsigned int: " + s); + } + } + } + + /** + * Checks if the given {@link SocketAddress} can be mapped to an {@link AFSocketAddress}. This is + * the case if the address either already is an {@link AFSocketAddress}, {@code null}, or + * something that has an equivalent representation, such as {@code UnixDomainSocketAddress}. + * + * @param addr The address. + * @return {@code true} if mappable. + */ + public static boolean canMap(SocketAddress addr) { + return canMap(addr, AFSocketAddress.class); + } + + /** + * Checks if the given {@link SocketAddress} can be mapped to a specific {@link AFSocketAddress} + * subclass. This is the case if the address either already is such an {@link AFSocketAddress}, + * {@code null}, or something that has an equivalent representation, such as + * {@code UnixDomainSocketAddress}. + * + * @param addr The address. + * @param targetAddressClass The target address class to map to. + * @return {@code true} if mappable. + */ + public static boolean canMap(SocketAddress addr, + Class targetAddressClass) { + if (addr == null) { + return true; + } else if (targetAddressClass.isAssignableFrom(addr.getClass())) { + return true; + } + AFSupplier supplier = SocketAddressUtil.supplyAFSocketAddress(addr); + if (supplier == null) { + return false; + } + AFSocketAddress afAddr = supplier.get(); + if (afAddr == null) { + return false; + } + return (targetAddressClass.isAssignableFrom(afAddr.getClass())); + } + + /** + * Maps the given address to an {@link AFSocketAddress}. + * + * @param addr The address. + * @return The {@link AFSocketAddress}. + * @throws IllegalArgumentException if the address could not be mapped. + * @see #canMap(SocketAddress,Class) + */ + public static AFSocketAddress mapOrFail(SocketAddress addr) { + return mapOrFail(addr, AFSocketAddress.class); + } + + /** + * Maps the given address to a specific {@link AFSocketAddress} type. + * + * @param addr The address. + * @param targetAddressClass The target address class. + * @param The target address type. + * @return The {@link AFSocketAddress}. + * @throws IllegalArgumentException if the address could not be mapped. + * @see #canMap(SocketAddress,Class) + */ + @SuppressWarnings("null") + public static A mapOrFail(SocketAddress addr, + Class targetAddressClass) { + if (addr == null) { + return null; + } else if (targetAddressClass.isAssignableFrom(addr.getClass())) { + return targetAddressClass.cast(addr); + } + + AFSupplier supplier = SocketAddressUtil.supplyAFSocketAddress(addr); + if (supplier == null) { + throw new IllegalArgumentException("Can only bind to endpoints of type " + + AFSocketAddress.class.getName() + ": " + addr); + } + AFSocketAddress afAddr = supplier.get(); + if (afAddr == null || !targetAddressClass.isAssignableFrom(afAddr.getClass())) { + throw new IllegalArgumentException("Can only bind to endpoints of type " + + AFSocketAddress.class.getName() + ", and this specific address is unsupported: " + + addr); + } + return targetAddressClass.cast(afAddr); + } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketAddressConfig.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketAddressConfig.java index f4b6bc194..d71cc0a67 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketAddressConfig.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketAddressConfig.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,8 @@ */ package org.newsclub.net.unix; +import java.io.IOException; +import java.net.SocketAddress; import java.net.SocketException; import java.net.URI; import java.util.Set; @@ -25,7 +27,7 @@ /** * The implementation-specifics for a given {@link AFSocketAddress} subclass implementation. - * + * * @param The supported address type. * @author Christian Kohlschütter * @see AFAddressFamilyConfig @@ -40,7 +42,7 @@ protected AFSocketAddressConfig() { /** * Tries to parse the given address-specific URI. - * + * * @param u The URI. * @param port The port to use, or {@code -1} for "unspecified". * @return The address. @@ -50,24 +52,34 @@ protected AFSocketAddressConfig() { /** * Returns the implementation's address constructor. - * + * * @return The implementation's address constructor. */ protected abstract AFSocketAddressConstructor addressConstructor(); /** * Returns the name of the implementation's selector provider class. - * + * * @return The name of the implementation's selector provider class. */ protected abstract String selectorProviderClassname(); /** * Returns the set of supported URI schemes that can be parsed via {@link #parseURI(URI,int)}. - * + * * These schemes must be unique to this {@link AFSocketAddress} type. - * + * * @return The set of supported URI schemes. */ protected abstract Set uriSchemes(); + + /** + * Returns an appropriate SocketAddress to be used when calling bind with a null argument. + * + * @return The new socket address, or {@code null}. + * @throws IOException on error. + */ + protected SocketAddress nullBindAddress() throws IOException { + return null; + } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketAddressFromHostname.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketAddressFromHostname.java index c7d67a8af..42ac35f21 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketAddressFromHostname.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketAddressFromHostname.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,13 +22,14 @@ /** * Helps converting an internet "hostname" to an {@link AFSocketAddress}. - * + * * @author Christian Kohlschütter + * @param The supported address type. */ public interface AFSocketAddressFromHostname { /** * Translates a "host" string (and port) to an {@link AFSocketAddress}. - * + * * @param host The hostname * @param port The port, or 0. * @return The {@link AFSocketAddress} @@ -40,7 +41,7 @@ public interface AFSocketAddressFromHostname { /** * Checks whether the given hostname is supported by this socket factory. If not, calls to * createSocket will cause a {@link SocketException}. - * + * * @param host The host to check. * @return {@code true} if supported. */ diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketCapability.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketCapability.java index 0fb7019db..5b6e5cec3 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketCapability.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketCapability.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,9 +22,16 @@ /** * Describes junixsocket capabilities the current environment (system platform, native library, * etc.) may or may not support. - * + * * You can check whether your environment supports a given capability by calling * {@link AFSocket#supports(AFSocketCapability)}. + * + * You can also manually disable a given capability by specifying a System property of the form + * org.newsclub.net.unix.library.disable.CAPABILITY_SOMETHING_SOMETHING=true + * when invoking the JVM (make sure this property is set before junixsocket is accessed). + * + * A simple way to check which capabilities are supported in an environment is to run the + * `junixsocket-selftest` jar. */ public enum AFSocketCapability { // see org_newsclub_net_unix_NativeUnixSocket.c in junixsocket-native @@ -41,16 +48,16 @@ public enum AFSocketCapability { /** Socket addressing supports the abstract namespace (Linux). */ CAPABILITY_ABSTRACT_NAMESPACE(3), - /** Support for AF_UNIX datagrams (not on Windows yet). */ + /** Support for AF_UNIX datagrams. */ CAPABILITY_UNIX_DATAGRAMS(4), /** * A pair of interconnected sockets can be created natively as AF_UNIX sockets. - * + * * This currently not possible on Windows, but instead emulated via anonymous AF_INET ports when * you use {@link AFSocketPair}. Other systems may provide partial implementations of pipe-based * (i.e., non-socket) pairs. - * + * * This capability is specific to AF_UNIX sockets. Other sockets, such as AF_VSOCK, may not * implement socketpair natively even if this capability is set, but would work-around that * limitation in a similar fashion but maybe without resorting to AF_INET. @@ -59,14 +66,14 @@ public enum AFSocketCapability { /** * A file descriptor can be converted to {@link Redirect}. - * + * * This feature currently uses Java SDK internals that may change/disappear. */ CAPABILITY_FD_AS_REDIRECT(6), /** * Support for AF_TIPC. - * + * * Availability of this feature is checked upon launch and therefore loading the "tipc" kernel * module at a later point may not be properly reflected. */ @@ -74,10 +81,10 @@ public enum AFSocketCapability { /** * Support for AF_UNIX. - * + * * Availability of this feature is checked upon launch and therefore, on systems adding support at * a later point, may not be properly reflected when checking at a later point. - * + * * NOTE: While this capability is typically supported on most systems that can actually load a * junixsocket JNI library, it is unavailable for older Windows versions (such as 8.1, 10 before * AFUNIX.SYS was included, etc.) and on systems where support for UNIX domain sockets is actively @@ -90,7 +97,7 @@ public enum AFSocketCapability { * * Availability of this feature is checked upon launch and therefore enabling vsock at a later * point may not be properly reflected. - * + * * @see #CAPABILITY_VSOCK_DGRAM */ CAPABILITY_VSOCK(9), @@ -106,16 +113,39 @@ public enum AFSocketCapability { /** * Support for zero-length send(2). - * + * * This can be used to perform a connection check, but not all operating systems support this or * behave correctly (particularly, IBM AIX, IBM i, and IBM z/OS) at the moment. - * + * * If not supported, junixsocket will simply ignore writes of zero-length, and connection checking * with {@link AFSocket#checkConnectionClosed()} may return {@code false} regardless of the actual * condition. */ CAPABILITY_ZERO_LENGTH_SEND(11), + /** + * Support for "unsafe" operations. + * + * Trading-in safety for speed or simplicity may be justified sometimes. + * + * @see Unsafe + * @see AFSocket#ensureUnsafeSupported() + */ + CAPABILITY_UNSAFE(12), + + /** + * Support for port numbers larger than 65535 (0xffff). + * + * Not all systems allow setting port numbers beyond the default TCP range (we use JNI tricks for + * that). This capability is required for RMI support. + */ + CAPABILITY_LARGE_PORTS(13), + + /** + * Support for certain Darwin (macOS Kernel)-specific features, such as the AF_SYSTEM domain. + */ + CAPABILITY_DARWIN(14), + ; // end of list private final int bitmask; diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketChannel.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketChannel.java index 5c05353df..e971bc759 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketChannel.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketChannel.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,14 @@ */ package org.newsclub.net.unix; +import static java.util.Objects.requireNonNull; + import java.io.FileDescriptor; import java.io.IOException; +import java.net.ProtocolFamily; import java.net.SocketAddress; import java.net.SocketOption; +import java.net.StandardProtocolFamily; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.nio.channels.spi.SelectorProvider; @@ -34,22 +38,22 @@ /** * A selectable channel for stream-oriented connecting sockets. - * + * * @param The concrete {@link AFSocketAddress} that is supported by this type. * @author Christian Kohlschütter */ public abstract class AFSocketChannel extends SocketChannel implements - AFSomeSocket, AFSocketExtensions { + AFSomeSocket, AFSocketExtensions, AFSomeSocketChannel { private final @NonNull AFSocket afSocket; private final AtomicBoolean connectPending = new AtomicBoolean(false); /** * Creates a new socket channel for the given socket, using the given {@link SelectorProvider}. - * + * * @param socket The socket. * @param sp The {@link SelectorProvider}. */ - @SuppressWarnings("null") + @SuppressWarnings("all") protected AFSocketChannel(AFSocket socket, AFSelectorProvider sp) { super(sp); this.afSocket = Objects.requireNonNull(socket); @@ -57,7 +61,7 @@ protected AFSocketChannel(AFSocket socket, AFSelectorProvider sp) { /** * Returns the corresponding {@link AFSocket}. - * + * * @return The corresponding socket. */ protected final AFSocket getAFSocket() { @@ -66,14 +70,14 @@ protected final AFSocket getAFSocket() { /** * A reference to a method that provides an {@link AFSocket} instance. - * + * * @param The concrete {@link AFSocketAddress} that is supported by this type. */ @FunctionalInterface protected interface AFSocketSupplier { /** * Returns a new {@link AFSocket} instance. - * + * * @return The instance. * @throws IOException on error. */ @@ -85,7 +89,7 @@ protected interface AFSocketSupplier { * * @param The concrete {@link AFSocketAddress} that is supported by this type. * @param supplier The AFSocketChannel constructor. - * + * * @return The new channel * @throws IOException on error. */ @@ -131,7 +135,7 @@ public final T getOption(SocketOption name) throws IOException { if (optionId == null) { throw new UnsupportedOperationException("unsupported option"); } else { - return (T) afSocket.getAFImpl().getOption(optionId.intValue()); + return (T) afSocket.getAFImpl().getOption(optionId); } } @@ -145,7 +149,7 @@ public final AFSocketChannel setOption(SocketOption name, T value) thr if (optionId == null) { throw new UnsupportedOperationException("unsupported option"); } else { - afSocket.getAFImpl().setOption(optionId.intValue(), value); + afSocket.getAFImpl().setOption(optionId, value); } return this; } @@ -195,11 +199,22 @@ public final boolean isConnectionPending() { @Override public final boolean connect(SocketAddress remote) throws IOException { - boolean connected = afSocket.connect0(remote, 0); - if (!connected) { - connectPending.set(true); + boolean complete = false; + Exception exception = null; + try { + begin(); + boolean connected = afSocket.connect0(remote, 0); + if (!connected) { + connectPending.set(true); + } + complete = true; + return connected; + } catch (IOException e) { + throw InterruptibleChannelUtil.ioExceptionOrThrowRuntimeException( // NOPMD.PreserveStackTrace + (exception = InterruptibleChannelUtil.handleException(this, e))); + } finally { + InterruptibleChannelUtil.endInterruptable(this, this::end, complete, exception); } - return connected; } @Override @@ -210,12 +225,23 @@ public final boolean finishConnect() throws IOException { return false; } - boolean connected = NativeUnixSocket.finishConnect(afSocket.getFileDescriptor()) - || isConnected(); - if (connected) { - connectPending.set(false); + boolean complete = false; + Exception exception = null; + try { + begin(); + boolean connected = NativeUnixSocket.finishConnect(afSocket.getFileDescriptor()) + || isConnected(); + if (connected) { + connectPending.set(false); + } + complete = true; + return connected; + } catch (IOException e) { + throw InterruptibleChannelUtil.ioExceptionOrThrowRuntimeException( // NOPMD.PreserveStackTrace + (exception = InterruptibleChannelUtil.handleException(this, e))); + } finally { + InterruptibleChannelUtil.endInterruptable(this, this::end, complete, exception); } - return connected; } @Override @@ -230,7 +256,19 @@ public final A getRemoteSocketAddress() { @Override public final int read(ByteBuffer dst) throws IOException { - return afSocket.getAFImpl().read(dst, null); + boolean complete = false; + Exception exception = null; + try { + begin(); + int read = afSocket.getAFImpl().read(dst, null); + complete = true; + return read; + } catch (IOException e) { + throw InterruptibleChannelUtil.ioExceptionOrThrowRuntimeException( // NOPMD.PreserveStackTrace + (exception = InterruptibleChannelUtil.handleException(this, e))); + } finally { + InterruptibleChannelUtil.endInterruptable(this, this::end, complete, exception); + } } @Override @@ -253,7 +291,19 @@ public final long write(ByteBuffer[] srcs, int offset, int length) throws IOExce @Override public final int write(ByteBuffer src) throws IOException { - return afSocket.getAFImpl().write(src); + boolean complete = false; + Exception exception = null; + try { + begin(); + int written = afSocket.getAFImpl().write(src); + complete = true; + return written; + } catch (IOException e) { + throw InterruptibleChannelUtil.ioExceptionOrThrowRuntimeException( // NOPMD.PreserveStackTrace + (exception = InterruptibleChannelUtil.handleException(this, e))); + } finally { + InterruptibleChannelUtil.endInterruptable(this, this::end, complete, exception); + } } @Override @@ -304,4 +354,35 @@ public final FileDescriptor getFileDescriptor() throws IOException { public final String toString() { return super.toString() + afSocket.toStringSuffix(); } + + @Override + public void setShutdownOnClose(boolean enabled) { + getAFCore().setShutdownOnClose(enabled); + } + + /** + * Opens a socket channel. The {@code family} parameter specifies the {@link ProtocolFamily + * protocol family} of the channel's socket. + *

+ * If the {@link ProtocolFamily} is of an {@link AFProtocolFamily}, or {@code UNIX}, the + * corresponding junixsocket implementation is used. In all other cases, the call is delegated to + * {@link SocketChannel#open()}. + * + * @param family The protocol family. + * @return The new {@link SocketChannel}. + * @throws IOException on error. + */ + public static SocketChannel open(ProtocolFamily family) throws IOException { + requireNonNull(family); + + if (family instanceof AFProtocolFamily) { + return ((AFProtocolFamily) family).openSocketChannel(); + } else if ("UNIX".equals(family.name())) { + return AFUNIXSocketChannel.open(); + } else if (family instanceof StandardProtocolFamily) { + return SocketChannel.open(); + } else { + throw new UnsupportedOperationException("Protocol family not supported"); + } + } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketConnector.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketConnector.java new file mode 100644 index 000000000..94ace570b --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketConnector.java @@ -0,0 +1,40 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.IOException; + +/** + * Some connector that is able to connect to a given {@link AFSocketAddress}. + * + * @param The address type to connect to. + * @param The address type for the returned socket (which should either be identical to + * {@code A} or {@link AFSocketAddress} to indicate that this could be any socket). + * @author Christian Kohlschütter + * @see AFServerSocketConnector + */ +public interface AFSocketConnector { + /** + * Connect to the socket at the given address. + * + * @param addr The address to connect to. + * @return The connected socket. + * @throws IOException on error. + */ + AFSocket connect(A addr) throws IOException; +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketCore.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketCore.java index 9fa3276ff..16422e200 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketCore.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketCore.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,15 +20,20 @@ import java.io.FileDescriptor; import java.io.IOException; import java.lang.reflect.InvocationTargetException; +import java.net.SocketException; import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import org.newsclub.net.unix.pool.ObjectPool.Lease; + /** * A shared core that is common for all AF* sockets (datagrams, streams). - * + * * @author Christian Kohlschütter */ class AFSocketCore extends AFCore { + private final AtomicInteger pendingAccepts = new AtomicInteger(0); private static final int SHUT_RD_WR = 2; /** @@ -39,6 +44,7 @@ class AFSocketCore extends AFCore { AFSocketAddress socketAddress; private final AFAddressFamily af; + private boolean shutdownOnClose = true; protected AFSocketCore(Object observed, FileDescriptor fd, AncillaryDataSupport ancillaryDataSupport, AFAddressFamily af, boolean datagramMode) { @@ -51,23 +57,31 @@ protected AFAddressFamily addressFamily() { } @Override + @SuppressWarnings("UnsafeFinalization" /* errorprone */) protected void doClose() throws IOException { - NativeUnixSocket.shutdown(fd, SHUT_RD_WR); - unblockAccepts(); + if (isShutdownOnClose()) { + NativeUnixSocket.shutdown(fd, SHUT_RD_WR); + unblockAccepts(); + } super.doClose(); } protected void unblockAccepts() { + // see AFSocketImpl } - AFSocketAddress receive(ByteBuffer dst) throws IOException { - ByteBuffer socketAddressBuffer = AFSocketAddress.SOCKETADDRESS_BUFFER_TL.get(); - int read = read(dst, socketAddressBuffer, 0); - if (read > 0) { - return AFSocketAddress.ofInternal(socketAddressBuffer, af); - } else { - return null; + AFSocketAddress receive(ByteBuffer dst, AFSupplier socketTimeout) throws IOException { + try (Lease socketAddressBufferLease = AFSocketAddress.SOCKETADDRESS_BUFFER_TL + .take()) { + ByteBuffer socketAddressBuffer = socketAddressBufferLease.get(); + + int read = read(dst, socketTimeout, socketAddressBuffer, 0); + if (read > 0) { + return AFSocketAddress.ofInternal(socketAddressBuffer, af); + } else { + return null; + } } } @@ -91,13 +105,14 @@ boolean isConnected(boolean boundOk) { return false; } - @SuppressWarnings({"unchecked", "null"}) + @SuppressWarnings({"unchecked"}) T getOption(AFSocketOption name) throws IOException { Class type = name.type(); if (Boolean.class.isAssignableFrom(type)) { return (T) (Object) (NativeUnixSocket.getSocketOption(fd, name.level(), name.optionName(), - Integer.class).intValue() != 0); + Integer.class) != 0); } else if (NamedInteger.HasOfValue.class.isAssignableFrom(type)) { + @SuppressWarnings("all") // "null" creates another warning int v = NativeUnixSocket.getSocketOption(fd, name.level(), name.optionName(), Integer.class); try { return (T) type.getMethod("ofValue", int.class).invoke(null, v); @@ -113,7 +128,7 @@ T getOption(AFSocketOption name) throws IOException { void setOption(AFSocketOption name, T value) throws IOException { final Object val; if (value instanceof Boolean) { - val = (((Boolean) value).booleanValue() ? 1 : 0); + val = (((Boolean) value) ? 1 : 0); } else if (value instanceof NamedInteger) { val = ((NamedInteger) value).value(); } else { @@ -132,4 +147,27 @@ void setOption(AFSocketOption name, T value) throws IOException { } } } + + protected void incPendingAccepts() throws SocketException { + if (pendingAccepts.incrementAndGet() >= Integer.MAX_VALUE) { + pendingAccepts.decrementAndGet(); + throw new SocketException("Too many pending accepts"); + } + } + + protected void decPendingAccepts() { + pendingAccepts.decrementAndGet(); + } + + protected boolean hasPendingAccepts() { + return pendingAccepts.get() > 0; + } + + boolean isShutdownOnClose() { + return shutdownOnClose; + } + + void setShutdownOnClose(boolean enabled) { + this.shutdownOnClose = enabled; + } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketExtensions.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketExtensions.java index 5ea9f9dd1..bcc58e4b0 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketExtensions.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketExtensions.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,35 +20,35 @@ /** * Defines certain methods that all junixsocket socket implementations share and extend beyond the * standard socket API. - * + * * The set of features include methods to support working with ancillary messages (such as file * descriptors) as well as socket credentials. - * + * * Keep in mind that the platform this code runs on may not support these features, and exceptions * may be thrown when not checking for the corresponding {@link AFSocketCapability} first. - * + * * @author Christian Kohlschütter */ public interface AFSocketExtensions { /** * Returns the size of the receive buffer for ancillary messages (in bytes). - * + * * @return The size. */ int getAncillaryReceiveBufferSize(); /** * Sets the size of the receive buffer for ancillary messages (in bytes). - * + * * To disable handling ancillary messages, set it to 0 (default). - * + * * @param size The size. */ void setAncillaryReceiveBufferSize(int size); /** * Ensures a minimum ancillary receive buffer size. - * + * * @param minSize The minimum size (in bytes). */ void ensureAncillaryReceiveBufferSize(int minSize); diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketFactory.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketFactory.java index e4aba2fdc..dd7d38bb7 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketFactory.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketFactory.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,29 +28,35 @@ /** * The base for a SocketFactory that connects to UNIX sockets. - * + * * Typically, the "hostname" is used as a reference to a socketFile on the file system. The actual * mapping is left to the implementor. - * + * * @see AFUNIXSocketFactory + * @param The supported address type. */ public abstract class AFSocketFactory extends SocketFactory implements AFSocketAddressFromHostname { + private final Class socketAddressClass; + /** * Creates a new socket factory instance. + * + * @param socketAddressClass The AFSocketAddress subclass. */ - protected AFSocketFactory() { + protected AFSocketFactory(Class socketAddressClass) { super(); + this.socketAddressClass = socketAddressClass; } /** * Checks whether the given {@link InetAddress} is supported by this socket factory. If not, calls * to createSocket will cause a {@link SocketException}. - * + * * By default, this only checks the hostname part of the address via * {@link #isHostnameSupported(String)}. - * + * * @param address The address to check. * @return {@code true} if supported. */ @@ -63,7 +69,7 @@ protected final boolean isInetAddressSupported(InetAddress address) { /** * Creates a new {@link AFSocket}, connected to the given address. - * + * * @param addr The address to connect to. * @return The socket instance. * @throws IOException on error. @@ -72,8 +78,8 @@ protected final boolean isInetAddressSupported(InetAddress address) { @SuppressWarnings("unchecked") private Socket connectTo(SocketAddress addr) throws IOException { - if (addr instanceof AFSocketAddress) { - return connectTo((A) addr); + if (AFSocketAddress.canMap(addr, socketAddressClass)) { + return connectTo((A) AFSocketAddress.mapOrFail(addr, socketAddressClass)); } else { Socket sock = new Socket(); sock.connect(addr); @@ -140,11 +146,11 @@ public static final class FixedAddressSocketFactory extends AFSocketFactory socket = ((AFSocketAddress) forceAddr).getAddressFamily().newSocket(); + if (AFSocketAddress.canMap(forceAddr)) { + AFSocket socket = AFSocketAddress.mapOrFail(forceAddr).getAddressFamily().newSocket(); socket.forceConnectAddress(forceAddr); return socket; } else { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketImpl.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketImpl.java index 9b9d59632..bed10c9b8 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketImpl.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketImpl.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,10 @@ */ package org.newsclub.net.unix; +import static org.newsclub.net.unix.NativeUnixSocket.SHUT_RD; +import static org.newsclub.net.unix.NativeUnixSocket.SHUT_RD_WR; +import static org.newsclub.net.unix.NativeUnixSocket.SHUT_WR; + import java.io.EOFException; import java.io.FileDescriptor; import java.io.IOException; @@ -31,23 +35,27 @@ import java.net.SocketOptions; import java.net.SocketTimeoutException; import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import org.newsclub.net.unix.pool.MutableHolder; +import org.newsclub.net.unix.pool.ObjectPool.Lease; /** * junixsocket-based {@link SocketImpl}. - * + * * @author Christian Kohlschütter + * @param The supported address type. */ -@SuppressWarnings({"PMD.CyclomaticComplexity"}) +@SuppressWarnings({ + "PMD.CyclomaticComplexity", "PMD.CouplingBetweenObjects", + "UnsafeFinalization" /* errorprone */}) public abstract class AFSocketImpl extends SocketImplShim { - private static final int SHUT_RD = 0; - private static final int SHUT_WR = 1; - private static final int SHUT_RD_WR = 2; private static final int SHUTDOWN_RD_WR = (1 << SHUT_RD) | (1 << SHUT_WR); private final AFSocketStreamCore core; @@ -72,6 +80,8 @@ public abstract class AFSocketImpl extends SocketImpl private AFSocketImplExtensions implExtensions = null; + private final AtomicBoolean closed = new AtomicBoolean(false); + /** * When the {@link AFSocketImpl} becomes unreachable (but not yet closed), we must ensure that the * underlying socket and all related file descriptors are closed. @@ -79,24 +89,12 @@ public abstract class AFSocketImpl extends SocketImpl * @author Christian Kohlschütter */ static final class AFSocketStreamCore extends AFSocketCore { - private final AtomicInteger pendingAccepts = new AtomicInteger(0); - AFSocketStreamCore(AFSocketImpl observed, FileDescriptor fd, AncillaryDataSupport ancillaryDataSupport, AFAddressFamily af) { super(observed, fd, ancillaryDataSupport, af, false); } - private void incPendingAccepts() throws SocketException { - if (pendingAccepts.incrementAndGet() >= Integer.MAX_VALUE) { - throw new SocketException("Too many pending accepts"); - } - } - - private void decPendingAccepts() throws SocketException { - pendingAccepts.decrementAndGet(); - } - - protected void createSocket(FileDescriptor fdTarget, AFSocketType type) throws IOException { + void createSocket(FileDescriptor fdTarget, AFSocketType type) throws IOException { NativeUnixSocket.createSocket(fdTarget, addressFamily().getDomain(), type.getId()); } @@ -109,31 +107,43 @@ protected void unblockAccepts() { if (socketAddress == null || socketAddress.getBytes() == null || inode.get() < 0) { return; } + if (!hasPendingAccepts()) { + return; + } + try { + ThreadUtil.runOnSystemThread(this::unblockAccepts0); + } catch (InterruptedException e) { + // ignore + } + } - while (pendingAccepts.get() > 0) { + private void unblockAccepts0() { + while (hasPendingAccepts()) { try { FileDescriptor tmpFd = new FileDescriptor(); - try { + try (Lease abLease = socketAddress.getNativeAddressDirectBuffer()) { createSocket(tmpFd, AFSocketType.SOCK_STREAM); - ByteBuffer ab = socketAddress.getNativeAddressDirectBuffer(); + ByteBuffer ab = abLease.get(); NativeUnixSocket.connect(ab, ab.limit(), tmpFd, inode.get()); } catch (IOException e) { // there's nothing more we can do to unlock these accepts // (e.g., SocketException: No such file or directory) return; } - try { - NativeUnixSocket.shutdown(tmpFd, SHUT_RD_WR); - } catch (Exception e) { - // ignore + if (isShutdownOnClose()) { + try { + NativeUnixSocket.shutdown(tmpFd, SHUT_RD_WR); + } catch (Exception e) { + // ignore + } } try { NativeUnixSocket.close(tmpFd); } catch (Exception e) { // ignore } - } catch (Exception e) { + } catch (RuntimeException e) { // ignore } @@ -149,13 +159,11 @@ protected void unblockAccepts() { /** * Creates a new {@link AFSocketImpl} instance. - * + * * @param addressFamily The address family. * @param fdObj The socket's {@link FileDescriptor}. - * @throws SocketException on error. */ - protected AFSocketImpl(AFAddressFamily<@NonNull A> addressFamily, FileDescriptor fdObj) - throws SocketException { + protected AFSocketImpl(AFAddressFamily<@NonNull A> addressFamily, FileDescriptor fdObj) { super(); this.addressFamily = addressFamily; this.address = InetAddress.getLoopbackAddress(); @@ -167,7 +175,7 @@ protected AFSocketImpl(AFAddressFamily<@NonNull A> addressFamily, FileDescriptor /** * Creates a new {@link InputStream} for this socket. - * + * * @return The new stream. */ protected final AFInputStream newInputStream() { @@ -176,7 +184,7 @@ protected final AFInputStream newInputStream() { /** * Creates a new {@link OutputStream} for this socket. - * + * * @return The new stream. */ protected final AFOutputStream newOutputStream() { @@ -221,7 +229,7 @@ final AFSocketCore getCore() { } boolean isClosed() { - return core.isClosed(); + return closed.get() || core.isClosed(); } // CPD-ON @@ -230,12 +238,15 @@ protected final void accept(SocketImpl socket) throws IOException { accept0(socket); } + @SuppressWarnings({ + "Finally" /* errorprone */, // + "PMD.CognitiveComplexity", "PMD.NPathComplexity", "PMD.NcssCount"}) final boolean accept0(SocketImpl socket) throws IOException { FileDescriptor fdesc = core.validFdOrException(); if (isClosed()) { - throw new SocketException("Socket is closed"); + throw new SocketClosedException(); } else if (!isBound()) { - throw new SocketException("Socket is not bound"); + throw new NotBoundSocketException(); } AFSocketAddress socketAddress = core.socketAddress; @@ -246,47 +257,105 @@ final boolean accept0(SocketImpl socket) throws IOException { } if (socketAddress == null) { - throw new SocketException("Socket is not bound"); + throw new NotBoundSocketException(); } @SuppressWarnings("unchecked") final AFSocketImpl si = (AFSocketImpl) socket; core.incPendingAccepts(); - try { - ByteBuffer ab = socketAddress.getNativeAddressDirectBuffer(); - SocketException caught = null; - try { - if (!NativeUnixSocket.accept(ab, ab.limit(), fdesc, si.fd, core.inode.get(), socketTimeout - .get())) { - return false; + final boolean virtualBlocking = (ThreadUtil.isVirtualThread() && core.isBlocking()) || core + .isVirtualBlocking(); + + long now = virtualBlocking ? System.currentTimeMillis() : 0; + boolean park = false; + virtualThreadLoop : do { + if (virtualBlocking) { + if (park) { + VirtualThreadPoller.INSTANCE.parkThreadUntilReady(fdesc, SelectionKey.OP_ACCEPT, now, + socketTimeout::get, this::close); } - } catch (SocketException e) { - caught = e; - } finally { // NOPMD.DoNotThrowExceptionInFinally - if (!isBound() || isClosed()) { + } + + try (Lease abLease = socketAddress.getNativeAddressDirectBuffer()) { + ByteBuffer ab = abLease.get(); + + SocketException caught = null; + try { + boolean success; + if (virtualBlocking) { + core.configureVirtualBlocking(true); + } try { - NativeUnixSocket.shutdown(si.fd, SHUT_RD_WR); - } catch (Exception e) { - // ignore + success = NativeUnixSocket.accept(ab, ab.limit(), fdesc, si.fd, core.inode.get(), + socketTimeout.get()); + } catch (SocketTimeoutException e) { + if (virtualBlocking) { + // try again + park = true; + continue virtualThreadLoop; + } else { + throw e; + } + } finally { + if (virtualBlocking) { + core.configureVirtualBlocking(false); + } + } + + if (virtualBlocking) { + if (success) { + // mark the accepted socket as blocking if necessary + NativeUnixSocket.configureBlocking(si.fd, core.isBlocking()); + } else { + // try again + park = true; + continue virtualThreadLoop; + } } + } catch (NotConnectedSocketException | SocketClosedException // NOPMD.ExceptionAsFlowControl + | BrokenPipeSocketException e) { try { - NativeUnixSocket.close(si.fd); - } catch (Exception e) { - // ignore + close(); + } catch (Exception e2) { + e.addSuppressed(e2); } - if (caught != null) { + throw e; + } catch (SocketException e) { // NOPMD.ExceptionAsFlowControl + caught = e; + } finally { // NOPMD.DoNotThrowExceptionInFinally + if (!isBound() || isClosed()) { + if (getCore().isShutdownOnClose()) { + try { + NativeUnixSocket.shutdown(si.fd, SHUT_RD_WR); + } catch (Exception e) { + // ignore + } + } + try { + NativeUnixSocket.close(si.fd); + } catch (Exception e) { + // ignore + } + if (caught != null) { + throw caught; + } else { + throw new BrokenPipeSocketException("Socket is closed"); + } + } else if (caught != null) { throw caught; - } else { - throw new SocketClosedException("Socket is closed"); } - } else if (caught != null) { - throw caught; } + } finally { + core.decPendingAccepts(); } - } finally { - core.decPendingAccepts(); + break; // NOPMD.AvoidBranchingStatementAsLastInLoop virtualThreadLoop + } while (true); // NOPMD.WhileLoopWithLiteralBoolean + + if (!si.fd.valid()) { + return false; } + si.setSocketAddress(socketAddress); si.connected.set(true); @@ -310,29 +379,36 @@ final void setSocketAddress(AFSocketAddress socketAddress) { @Override protected final int available() throws IOException { FileDescriptor fdesc = core.validFdOrException(); - return NativeUnixSocket.available(fdesc, core.getThreadLocalDirectByteBuffer(0)); + try (Lease> lease = core.getPrivateDirectByteBuffer(0)) { + return NativeUnixSocket.available(fdesc, lease.get().get()); + } } final void bind(SocketAddress addr, int options) throws IOException { if (addr == null) { - throw new IllegalArgumentException("Cannot bind to null address"); - } - if (!(addr instanceof AFSocketAddress)) { - throw new SocketException("Cannot bind to this type of address: " + addr.getClass()); + addr = addressFamily.nullBindAddress(); + if (addr == null) { + throw new UnsupportedOperationException("Cannot bind to null address"); + } } - bound.set(true); - if (addr == AFSocketAddress.INTERNAL_DUMMY_BIND) { // NOPMD + bound.set(true); core.inode.set(0); return; } - AFSocketAddress socketAddress = (AFSocketAddress) addr; + addr = AFSocketAddress.mapOrFail(addr, addressFamily.getSocketAddressClass()); + bound.set(true); + + AFSocketAddress socketAddress = Objects.requireNonNull((AFSocketAddress) addr); this.setSocketAddress(socketAddress); - ByteBuffer ab = socketAddress.getNativeAddressDirectBuffer(); - core.inode.set(NativeUnixSocket.bind(ab, ab.limit(), fd, options)); + try (Lease abLease = socketAddress.getNativeAddressDirectBuffer()) { + ByteBuffer ab = abLease.get(); + long inode = NativeUnixSocket.bind(ab, ab.limit(), fd, options); + core.inode.set(inode); + } core.validFdOrException(); } @@ -350,7 +426,13 @@ private void checkClose() throws IOException { @Override protected final void close() throws IOException { - shutdown(); + this.closed.set(true); + try { + shutdown(); + } catch (NotConnectedSocketException | SocketClosedException e) { + // ignore + } + core.runCleaner(); } @@ -371,50 +453,111 @@ protected final void connect(SocketAddress addr, int connectTimeout) throws IOEx connect0(addr, connectTimeout); } + @SuppressWarnings({"PMD.CognitiveComplexity", "PMD.NPathComplexity", "PMD.NcssCount"}) final boolean connect0(SocketAddress addr, int connectTimeout) throws IOException { if (addr == AFSocketAddress.INTERNAL_DUMMY_CONNECT) { // NOPMD this.connected.set(true); return true; - } else if (addr == AFSocketAddress.INTERNAL_DUMMY_CONNECT) { // NOPMD) + } else if (addr == AFSocketAddress.INTERNAL_DUMMY_DONT_CONNECT) { // NOPMD) return false; } - if (!(addr instanceof AFSocketAddress)) { - throw new SocketException("Cannot connect to this type of address: " + addr.getClass()); + addr = AFSocketAddress.mapOrFail(addr, addressFamily.getSocketAddressClass()); + AFSocketAddress socketAddress = Objects.requireNonNull((AFSocketAddress) addr); + + final boolean virtualBlocking = (ThreadUtil.isVirtualThread() && core.isBlocking()) || core + .isVirtualBlocking(); + long now = virtualBlocking ? System.currentTimeMillis() : 0; + + /** + * If set, a two-phase connect is in flight, and the value holds the connect timeout. + */ + AFSupplier virtualConnectTimeout = null; + + if (virtualBlocking) { + core.configureVirtualBlocking(true); } + boolean park = false; + try { + virtualThreadLoop : do { + try (Lease abLease = socketAddress.getNativeAddressDirectBuffer()) { + ByteBuffer ab = abLease.get(); + boolean success = false; + boolean ignoreSpuriousTimeout = true; + do { + if (virtualBlocking) { + if (virtualConnectTimeout != null) { + if (park) { + VirtualThreadPoller.INSTANCE.parkThreadUntilReady(fd, SelectionKey.OP_CONNECT, + now, virtualConnectTimeout, this::close); + } + } else { + Thread.yield(); + } + } - AFSocketAddress socketAddress = (AFSocketAddress) addr; - ByteBuffer ab = socketAddress.getNativeAddressDirectBuffer(); - boolean success = false; - boolean ignoreSpuriousTimeout = true; - do { - try { - success = NativeUnixSocket.connect(ab, ab.limit(), fd, -1); - break; - } catch (SocketTimeoutException e) { - // Ignore spurious timeout once when SO_TIMEOUT==0 - // seen on older Linux kernels with AF_VSOCK running in qemu - if (ignoreSpuriousTimeout) { - Object o = getOption(SocketOptions.SO_TIMEOUT); - if (o instanceof Integer) { - if (((Integer) o).intValue() == 0) { - ignoreSpuriousTimeout = false; - continue; + if (virtualBlocking) { + core.configureVirtualBlocking(true); + } + try { + success = NativeUnixSocket.connect(ab, ab.limit(), fd, -2); + if (!success && virtualBlocking) { + // try again (non-blocking timeout) + if (virtualConnectTimeout == null) { + virtualConnectTimeout = () -> connectTimeout; + } + park = true; + continue virtualThreadLoop; + } + break; + } catch (SocketTimeoutException e) { + // Ignore spurious timeout once when SO_TIMEOUT==0 + // seen on older Linux kernels with AF_VSOCK running in qemu + if (ignoreSpuriousTimeout) { + Object o = getOption(SocketOptions.SO_TIMEOUT); + if (o instanceof Integer) { + if (((Integer) o) == 0) { + ignoreSpuriousTimeout = false; + continue; + } + } else if (o == null) { + ignoreSpuriousTimeout = false; + continue; + } + } + throw e; + } catch (NotConnectedSocketException | SocketClosedException + | BrokenPipeSocketException e) { + try { + close(); + } catch (Exception e2) { + e.addSuppressed(e2); + } + throw e; + } catch (SocketException e) { + if (virtualBlocking) { + Thread.yield(); + } + throw e; + } finally { + if (virtualBlocking) { + core.configureVirtualBlocking(false); + } } - } else if (o == null) { - ignoreSpuriousTimeout = false; - continue; + } while (ThreadUtil.checkNotInterruptedOrThrow()); + if (success) { + setSocketAddress(socketAddress); + this.connected.set(true); } + core.validFdOrException(); + return success; } - throw e; + } while (true); // NOPMD.WhileLoopWithLiteralBoolean + } finally { + if (virtualBlocking) { + core.configureVirtualBlocking(true); } - } while (!Thread.interrupted()); - if (success) { - setSocketAddress(socketAddress); - this.connected.set(true); } - core.validFdOrException(); - return success; } @Override @@ -424,7 +567,7 @@ protected final void create(boolean stream) throws IOException { } if (fd.valid()) { if (createType != null) { - if (createType.booleanValue() != stream) { + if (createType.booleanValue() != stream) { // NOPMD.UnnecessaryBoxing throw new IllegalStateException("Already created with different mode"); } } else { @@ -439,6 +582,7 @@ protected final void create(boolean stream) throws IOException { @Override protected final AFInputStream getInputStream() throws IOException { if (!isConnected() && !isBound()) { + close(); throw new SocketClosedException("Not connected/not bound"); } core.validFdOrException(); @@ -448,6 +592,7 @@ protected final AFInputStream getInputStream() throws IOException { @Override protected final AFOutputStream getOutputStream() throws IOException { if (!isClosed() && !isBound()) { + close(); throw new SocketClosedException("Not connected/not bound"); } core.validFdOrException(); @@ -477,8 +622,9 @@ private final class AFInputStreamImpl extends AFInputStream { private volatile boolean streamClosed = false; private final AtomicBoolean eofReached = new AtomicBoolean(false); - private final int opt = (core.isBlocking() ? 0 : NativeUnixSocket.OPT_NON_BLOCKING); + private final int defaultOpt = (core.isBlocking() ? 0 : NativeUnixSocket.OPT_NON_BLOCKING); + @SuppressWarnings("PMD.CognitiveComplexity") @Override public int read(byte[] buf, int off, int len) throws IOException { if (streamClosed) { @@ -495,15 +641,65 @@ public int read(byte[] buf, int off, int len) throws IOException { throw new IndexOutOfBoundsException(); } - try { - return NativeUnixSocket.read(fdesc, buf, off, len, opt, ancillaryDataSupport, socketTimeout - .get()); - } catch (EOFException e) { - eofReached.set(true); - throw e; + final boolean virtualBlocking = (ThreadUtil.isVirtualThread() && core.isBlocking()) || core + .isVirtualBlocking(); + final long now; + final int opt; + if (virtualBlocking) { + now = System.currentTimeMillis(); + opt = defaultOpt | NativeUnixSocket.OPT_NON_BLOCKING; + } else { + now = 0; + opt = defaultOpt; } + + int read; + + boolean park = false; + virtualThreadLoop : do { + if (virtualBlocking) { + if (park) { + VirtualThreadPoller.INSTANCE.parkThreadUntilReady(fdesc, SelectionKey.OP_READ, now, + socketTimeout::get, this::forceCloseSocket); + } + core.configureVirtualBlocking(true); + } + + try { + read = NativeUnixSocket.read(fdesc, buf, off, len, opt, ancillaryDataSupport, + socketTimeout.get()); + if (read == -2) { + if (virtualBlocking) { + // sleep again + park = true; + continue virtualThreadLoop; + } else { + read = 0; + } + } + } catch (SocketTimeoutException e) { + if (virtualBlocking) { + // sleep again + park = true; + continue virtualThreadLoop; + } else { + throw e; + } + } catch (EOFException e) { + eofReached.set(true); + throw e; + } finally { + if (virtualBlocking) { + core.configureVirtualBlocking(false); + } + } + break; // NOPMD.AvoidBranchingStatementAsLastInLoop virtualThreadLoop + } while (true); // NOPMD.WhileLoopWithLiteralBoolean + + return read; } + @SuppressWarnings("PMD.CognitiveComplexity") @Override public int read() throws IOException { FileDescriptor fdesc = core.validFdOrException(); @@ -512,21 +708,78 @@ public int read() throws IOException { return -1; } - int byteRead = NativeUnixSocket.read(fdesc, null, 0, 1, opt, ancillaryDataSupport, - socketTimeout.get()); - if (byteRead < 0) { - eofReached.set(true); - return -1; + // CPD-OFF + final boolean virtualBlocking = (ThreadUtil.isVirtualThread() && core.isBlocking()) || core + .isVirtualBlocking(); + final long now; + final int opt; + if (virtualBlocking) { + now = System.currentTimeMillis(); + opt = defaultOpt | NativeUnixSocket.OPT_NON_BLOCKING; } else { - return byteRead; + now = 0; + opt = defaultOpt; } + + boolean park = false; + virtualThreadLoop : do { + if (virtualBlocking) { + if (park) { + VirtualThreadPoller.INSTANCE.parkThreadUntilReady(fdesc, SelectionKey.OP_READ, now, + socketTimeout::get, this::forceCloseSocket); + } + core.configureVirtualBlocking(true); + } + + try { + int byteRead = NativeUnixSocket.read(fdesc, null, 0, 1, opt, ancillaryDataSupport, + socketTimeout.get()); + if (byteRead < 0) { + if (byteRead == -2) { + if (virtualBlocking) { + // sleep again + park = true; + continue virtualThreadLoop; + } else { + byteRead = -1; + } + } + eofReached.set(true); + return -1; + } else { + return byteRead; + } + } catch (SocketTimeoutException e) { + if (virtualBlocking) { + // sleep again + park = true; + continue virtualThreadLoop; + } else { + throw e; + } + } finally { + if (virtualBlocking) { + core.configureVirtualBlocking(false); + } + } + } while (true); // NOPMD.WhileLoopWithLiteralBoolean + + // CPD-ON + } + + private void forceCloseSocket() throws IOException { + closedOutputStream = true; + close(); } @Override public synchronized void close() throws IOException { + if (streamClosed || isClosed()) { + return; + } streamClosed = true; FileDescriptor fdesc = core.validFd(); - if (fdesc != null) { + if (fdesc != null && getCore().isShutdownOnClose()) { NativeUnixSocket.shutdown(fdesc, SHUT_RD); } @@ -547,14 +800,14 @@ public int available() throws IOException { public FileDescriptor getFileDescriptor() throws IOException { return getFD(); } + } private static boolean checkWriteInterruptedException(int bytesTransferred) throws InterruptedIOException { - if (Thread.interrupted()) { - InterruptedIOException ex = new InterruptedIOException("Thread interrupted during write"); + if (Thread.currentThread().isInterrupted()) { + InterruptedIOException ex = new InterruptedIOException("write"); ex.bytesTransferred = bytesTransferred; - Thread.currentThread().interrupt(); throw ex; } return true; @@ -563,21 +816,73 @@ private static boolean checkWriteInterruptedException(int bytesTransferred) private final class AFOutputStreamImpl extends AFOutputStream { private volatile boolean streamClosed = false; - private final int opt = (core.isBlocking() ? 0 : NativeUnixSocket.OPT_NON_BLOCKING); + private final int defaultOpt = (core.isBlocking() ? 0 : NativeUnixSocket.OPT_NON_BLOCKING); + @SuppressWarnings("PMD.CognitiveComplexity") @Override public void write(int oneByte) throws IOException { FileDescriptor fdesc = core.validFdOrException(); - int written; - do { - written = NativeUnixSocket.write(fdesc, null, oneByte, 1, opt, ancillaryDataSupport); - if (written != 0) { - break; + final boolean virtualBlocking = (ThreadUtil.isVirtualThread() && core.isBlocking()) || core + .isVirtualBlocking(); + final long now; + final int opt; + if (virtualBlocking) { + now = System.currentTimeMillis(); + opt = defaultOpt | NativeUnixSocket.OPT_NON_BLOCKING; + } else { + now = 0; + opt = defaultOpt; + } + + boolean park = false; + virtualThreadLoop : do { + if (virtualBlocking) { + if (park) { + VirtualThreadPoller.INSTANCE.parkThreadUntilReady(fdesc, SelectionKey.OP_WRITE, now, + socketTimeout::get, this::forceCloseSocket); + } + core.configureVirtualBlocking(true); + } + + try { + int written; + do { + written = NativeUnixSocket.write(fdesc, null, oneByte, 1, opt, ancillaryDataSupport); + if (written != 0) { + break; + } + if (virtualBlocking) { + park = true; + continue virtualThreadLoop; + } + } while (checkWriteInterruptedException(0)); + } catch (NotConnectedSocketException | SocketClosedException + | BrokenPipeSocketException e) { + try { + forceCloseSocket(); + } catch (Exception e2) { + e.addSuppressed(e2); + } + throw e; + } catch (SocketTimeoutException e) { + if (virtualBlocking) { + // try again + park = true; + continue virtualThreadLoop; + } else { + throw e; + } + } finally { + if (virtualBlocking) { + core.configureVirtualBlocking(false); + } } - } while (checkWriteInterruptedException(0)); + break; // NOPMD.AvoidBranchingStatementAsLastInLoop virtualThreadLoop + } while (true); // NOPMD.WhileLoopWithLiteralBoolean } + @SuppressWarnings({"PMD.CognitiveComplexity", "PMD.NPathComplexity"}) @Override public void write(byte[] buf, int off, int len) throws IOException { if (streamClosed) { @@ -594,39 +899,97 @@ public void write(byte[] buf, int off, int len) throws IOException { return; } - int writtenTotal = 0; + final boolean virtualBlocking = (ThreadUtil.isVirtualThread() && core.isBlocking()) || core + .isVirtualBlocking(); + final long now; + final int opt; + if (virtualBlocking) { + now = System.currentTimeMillis(); + opt = defaultOpt | NativeUnixSocket.OPT_NON_BLOCKING; + } else { + now = 0; + opt = defaultOpt; + } + int writtenTotal = 0; do { - final int written = NativeUnixSocket.write(fdesc, buf, off, len, opt, ancillaryDataSupport); - if (written < 0) { - if (len == 0) { - // This exception is only useful to detect OS-level bugs that we need to work-around - // in native code. - // throw new IOException("Error while writing zero-length byte array; try -D" - // + AFSocket.PROP_LIBRARY_DISABLE_CAPABILITY_PREFIX - // + AFSocketCapability.CAPABILITY_ZERO_LENGTH_SEND.name() + "=true"); + boolean park = false; + virtualThreadLoop : do { + if (virtualBlocking) { + if (park) { + VirtualThreadPoller.INSTANCE.parkThreadUntilReady(fdesc, SelectionKey.OP_WRITE, now, + socketTimeout::get, this::forceCloseSocket); + } + core.configureVirtualBlocking(true); + } - // ignore - return; - } else { - throw new IOException("Unspecific error while writing"); + final int written; + try { + written = NativeUnixSocket.write(fdesc, buf, off, len, opt, ancillaryDataSupport); + if (written == 0 && virtualBlocking) { + // try again + park = true; + continue virtualThreadLoop; + } + if (written < 0) { + if (len == 0) { + // This exception is only useful to detect OS-level bugs that we need to + // work-around + // in native code. + // throw new IOException("Error while writing zero-length byte array; try -D" + // + AFSocket.PROP_LIBRARY_DISABLE_CAPABILITY_PREFIX + // + AFSocketCapability.CAPABILITY_ZERO_LENGTH_SEND.name() + "=true"); + + // ignore + return; + } else { + throw new IOException("Unspecific error while writing"); + } + } + } catch (NotConnectedSocketException | SocketClosedException + | BrokenPipeSocketException e) { + try { + forceCloseSocket(); + } catch (Exception e2) { + e.addSuppressed(e2); + } + throw e; + } catch (SocketTimeoutException e) { + if (virtualBlocking) { + // try again + park = true; + continue virtualThreadLoop; + } else { + throw e; + } + } finally { + if (virtualBlocking) { + core.configureVirtualBlocking(false); + } } - } - len -= written; - off += written; - writtenTotal += written; + len -= written; + off += written; + writtenTotal += written; + break; // NOPMD.AvoidBranchingStatementAsLastInLoop virtualThreadLoop + } while (true); // NOPMD.WhileLoopWithLiteralBoolean + } while (len > 0 && checkWriteInterruptedException(writtenTotal)); } + private void forceCloseSocket() throws IOException { + closedInputStream = true; + close(); + } + @Override public synchronized void close() throws IOException { - if (streamClosed) { + if (streamClosed || isClosed()) { return; } streamClosed = true; FileDescriptor fdesc = core.validFd(); - if (fdesc != null) { + if (fdesc != null && getCore().isShutdownOnClose()) { NativeUnixSocket.shutdown(fdesc, SHUT_WR); } closedOutputStream = true; @@ -663,7 +1026,7 @@ private static int expectBoolean(Object value) throws SocketException { new NullPointerException()); } try { - return ((Boolean) value).booleanValue() ? 1 : 0; + return ((Boolean) value) ? 1 : 0; } catch (final ClassCastException e) { throw (SocketException) new SocketException("Unsupported value: " + value).initCause(e); } @@ -747,7 +1110,7 @@ private void setOption0(int optID, Object value) throws SocketException { /** * Like {@link #getOption(int)}, but ignores exceptions for certain option IDs. - * + * * @param optID The option ID. * @return The value. * @throws SocketException on error. @@ -768,7 +1131,7 @@ protected final Object getOptionLenient(int optID) throws SocketException { /** * Like {@link #setOption(int, Object)}, but ignores exceptions for certain option IDs. - * + * * @param optID The option ID. * @param value The value. * @throws SocketException on error. @@ -804,8 +1167,16 @@ static final void setOptionDefault(FileDescriptor fdesc, int optID, Object value return; case SocketOptions.SO_TIMEOUT: { int timeout = expectInteger(value); - NativeUnixSocket.setSocketOptionInt(fdesc, 0x1005, timeout); - NativeUnixSocket.setSocketOptionInt(fdesc, 0x1006, timeout); + try { + NativeUnixSocket.setSocketOptionInt(fdesc, 0x1005, timeout); + } catch (InvalidArgumentSocketException e) { + // Perhaps the socket is shut down? + } + try { + NativeUnixSocket.setSocketOptionInt(fdesc, 0x1006, timeout); + } catch (InvalidArgumentSocketException e) { + // Perhaps the socket is shut down? + } if (acceptTimeout != null) { acceptTimeout.set(timeout); } @@ -844,7 +1215,7 @@ static final void setOptionDefault(FileDescriptor fdesc, int optID, Object value /** * Shuts down both input and output at once. Equivalent to calling {@link #shutdownInput()} and * {@link #shutdownOutput()}. - * + * * @throws IOException on error. */ protected final void shutdown() throws IOException { @@ -898,19 +1269,19 @@ AncillaryDataSupport getAncillaryDataSupport() { } final SocketAddress receive(ByteBuffer dst) throws IOException { - return core.receive(dst); + return core.receive(dst, socketTimeout::get); } final int send(ByteBuffer src, SocketAddress target) throws IOException { - return core.write(src, target, 0); + return core.write(src, socketTimeout::get, target, 0); } final int read(ByteBuffer dst, ByteBuffer socketAddressBuffer) throws IOException { - return core.read(dst, socketAddressBuffer, 0); + return core.read(dst, socketTimeout::get, socketAddressBuffer, 0); } final int write(ByteBuffer src) throws IOException { - return core.write(src); + return core.write(src, socketTimeout::get); } @Override @@ -963,7 +1334,7 @@ final AFAddressFamily getAddressFamily() { @Override protected void setOption(SocketOption name, T value) throws IOException { if (name instanceof AFSocketOption) { - ((AFSocketImpl) this).getCore().setOption((AFSocketOption) name, value); + getCore().setOption((AFSocketOption) name, value); return; } Integer optionId = SocketOptionsMapper.resolve(name); @@ -978,7 +1349,7 @@ protected void setOption(SocketOption name, T value) throws IOException { @Override protected T getOption(SocketOption name) throws IOException { if (name instanceof AFSocketOption) { - return ((AFSocketImpl) this).getCore().getOption((AFSocketOption) name); + return getCore().getOption((AFSocketOption) name); } Integer optionId = SocketOptionsMapper.resolve(name); if (optionId == null) { @@ -995,7 +1366,7 @@ protected Set> supportedOptions() { /** * Returns the internal helper instance for address-specific extensions. - * + * * @return The helper instance. * @throws UnsupportedOperationException if such extensions are not supported for this address * type. diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketImplExtensions.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketImplExtensions.java index 8e9ae8837..ee8eef261 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketImplExtensions.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketImplExtensions.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ /** * Placeholder for protocol-specific code that resides in the native library. - * + * * @param The corresponding address type. * @author Christian Kohlschütter * @see AFTIPCSocketImplExtensions diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketOption.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketOption.java index d69ef554b..595f7e65b 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketOption.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketOption.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ /** * A special socket option supported by some junixsocket-based implementation. - * + * * @param The option's value type. * @author Christian Kohlschütter */ @@ -33,7 +33,7 @@ public final class AFSocketOption implements SocketOption { /** * Creates a new socket option. This should only be called by {@link AFSocket} implementations. - * + * * @param name The name of the option. * @param type The value type. * @param level The socket level (as defined in junixsocket-native). diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketPair.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketPair.java index 051ee7f63..0682921fa 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketPair.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketPair.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,14 +23,14 @@ /** * A pair of sockets. - * + * * @param The socket type. * @author Christian Kohlschütter */ public abstract class AFSocketPair extends CloseablePair { /** * Creates a socket pair. - * + * * @param a The first socket. * @param b The second socket. */ @@ -40,7 +40,7 @@ protected AFSocketPair(T a, T b) { /** * Creates a socket pair. - * + * * @param a The first socket. * @param b The second socket. * @param alsoClose Some closeable that is also closed upon {@link #close()}, or {@code null}. @@ -51,7 +51,7 @@ protected AFSocketPair(T a, T b, Closeable alsoClose) { /** * Returns the first socket of the pair. - * + * * @return The first socket. */ public final @NonNull T getSocket1() { @@ -60,7 +60,7 @@ protected AFSocketPair(T a, T b, Closeable alsoClose) { /** * Returns the second socket of the pair. - * + * * @return The second socket. */ public final @NonNull T getSocket2() { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketProtocol.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketProtocol.java index f64fce676..b6a42c283 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketProtocol.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketProtocol.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ /** * Describes the "protocol" part of a socket. - * + * * @author Christian Kohlschütter */ public enum AFSocketProtocol { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketType.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketType.java index c0f65dec8..160b98764 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketType.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketType.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ /** * Describes the "type" of a socket. - * + * * @author Christian Kohlschütter */ public enum AFSocketType { @@ -34,18 +34,22 @@ public enum AFSocketType { SOCK_DGRAM(NativeUnixSocket.SOCK_DGRAM), // /** - * Sequential packet socket. + * Raw mode. */ - SOCK_SEQPACKET(NativeUnixSocket.SOCK_SEQPACKET), // + SOCK_RAW(NativeUnixSocket.SOCK_RAW), // /** * Reliably-delivered datagram messages. - * + * * Used by {@code AFTIPCDatagramSocket} to differentiate between datagram connects that may or may * not permit package loss. */ SOCK_RDM(NativeUnixSocket.SOCK_RDM), // + /** + * Sequential packet socket. + */ + SOCK_SEQPACKET(NativeUnixSocket.SOCK_SEQPACKET), // ; private final int id; diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSomeSocket.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSomeSocket.java index 7315c12de..5b0712d36 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSomeSocket.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSomeSocket.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ */ package org.newsclub.net.unix; -import java.io.Closeable; import java.net.DatagramSocket; import java.net.Socket; import java.net.SocketAddress; @@ -29,7 +28,7 @@ /** * Marker interface that combines junixsocket-based {@link SocketChannel}s, {@link Socket}s, * {@link DatagramChannel}s and {@link DatagramSocket}s. - * + * * @author Christian Kohlschütter * @see AFSocketPair * @see AFSocket @@ -37,20 +36,11 @@ * @see AFDatagramSocket * @see AFDatagramChannel */ -public interface AFSomeSocket extends Closeable, FileDescriptorAccess { - /** - * Returns the socket's local socket address, or {@code null} if unavailable or if there was a - * problem retrieving it. - * - * @return The local socket address, or {@code null}. - */ - @Nullable - SocketAddress getLocalSocketAddress(); - +public interface AFSomeSocket extends AFSomeSocketThing { /** * Returns the socket's remote socket address, or {@code null} if unavailable/not connected, or if * there was a problem retrieving it. - * + * * @return The remote socket address, or {@code null}. */ @Nullable diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSomeSocketChannel.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSomeSocketChannel.java new file mode 100644 index 000000000..d6dc9cf12 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSomeSocketChannel.java @@ -0,0 +1,59 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.IOException; +import java.nio.channels.DatagramChannel; +import java.nio.channels.InterruptibleChannel; +import java.nio.channels.SelectableChannel; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +/** + * Marker interface that combines junixsocket-based {@link SocketChannel}s, {@link DatagramChannel}s + * or {@link ServerSocketChannel}s. + * + * @author Christian Kohlschütter + * @see AFSocketChannel + * @see AFServerSocketChannel + * @see AFDatagramChannel + */ +public interface AFSomeSocketChannel extends InterruptibleChannel, FileDescriptorAccess, + AFSomeSocketThing { + /** + * Checks if the channel is configured blocking. The result may be cached, and therefore not + * invoke native code to check if the underlying socket is actually configured that way. + * + * @return {@code true} if blocking. + */ + boolean isBlocking(); + + /** + * Adjusts this channel's blocking mode. + * + *

+ * If the given blocking mode is different from the currently cached blocking mode then this + * method native code to change it. + *

+ * + * @param block {@code true} if blocking is desired. + * @return This channel. + * @throws IOException on error. + */ + SelectableChannel configureBlocking(boolean block) throws IOException; +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSomeSocketThing.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSomeSocketThing.java new file mode 100644 index 000000000..53b0a47d8 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSomeSocketThing.java @@ -0,0 +1,61 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.Closeable; +import java.net.DatagramSocket; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.nio.channels.DatagramChannel; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +import org.eclipse.jdt.annotation.Nullable; + +/** + * Marker interface that combines junixsocket-based {@link SocketChannel}s, {@link Socket}s, + * {@link DatagramChannel}s and {@link DatagramSocket}s, as well as {@link ServerSocket}s and + * {@link ServerSocketChannel}s. + * + * @author Christian Kohlschütter + * @see AFSocketPair + * @see AFSocket + * @see AFSocketChannel + * @see AFDatagramSocket + * @see AFDatagramChannel + * @see AFServerSocket + * @see AFServerSocketChannel + */ +public interface AFSomeSocketThing extends Closeable, FileDescriptorAccess { + /** + * Returns the socket's local socket address, or {@code null} if unavailable or if there was a + * problem retrieving it. + * + * @return The local socket address, or {@code null}. + */ + @Nullable + SocketAddress getLocalSocketAddress(); + + /** + * Configures whether the socket should be shutdown upon {@link #close()}, which is the default. + * + * @param enabled {@code true} if enabled. + */ + void setShutdownOnClose(boolean enabled); +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSupplier.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSupplier.java new file mode 100644 index 000000000..136d714e2 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSupplier.java @@ -0,0 +1,35 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +/** + * junixsocket-internal variant of {@code java.util.Supplier}, to allow compiling junixsocket-common + * with retrolambda for Java 1.7. + * + * @param the type of results supplied by this supplier + */ +@FunctionalInterface +interface AFSupplier { + + /** + * Gets a result. + * + * @return a result + */ + T get(); +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFTIPCSocketAddress.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFTIPCSocketAddress.java index 5efcb28ef..6726630c7 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFTIPCSocketAddress.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFTIPCSocketAddress.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,10 +37,11 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.newsclub.net.unix.pool.ObjectPool.Lease; /** * An {@link AFSocketAddress} for TIPC sockets. - * + * * The TIPC socket API provides three different address types: *
    *
  • Service Address. @@ -106,11 +107,11 @@ * indicated node. If this value is zero, all matching sockets in the whole cluster, as visible from * the source node, are eligible. *

    - * + * * @author Christian Kohlschütter (documentation credits to Jon Maloy and the TIPC team). */ public final class AFTIPCSocketAddress extends AFSocketAddress { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; // do not change! private static final Pattern PAT_TIPC_URI_HOST_AND_PORT = Pattern.compile( "^((?:(?:(?cluster|node|default|[0-9a-fx]+)\\-)?(?service|service-range|socket)\\.)|" @@ -131,7 +132,7 @@ public final class AFTIPCSocketAddress extends AFSocketAddress { /** * The TIPC address type. - * + * * @author Christian Kohlschütter */ @NonNullByDefault @@ -171,8 +172,8 @@ public static final class AddressType extends NamedInteger { private AddressType(int id) { super(id); - this.ds = (a, b, c) -> ":" + Integer.toUnsignedString(a) + ":" + Integer.toUnsignedString(b) - + ":" + Integer.toUnsignedString(c); + this.ds = (a, b, c) -> ":" + toUnsignedString(a) + ":" + toUnsignedString(b) + ":" + + toUnsignedString(c); } private AddressType(String name, int id, DebugStringProvider ds) { @@ -191,7 +192,7 @@ interface DebugStringProvider extends Serializable { /** * Formats an integer as an unsigned, zero-padded 32-bit hexadecimal number. - * + * * @param i The number. * @return The string. */ @@ -211,7 +212,7 @@ private String toDebugString(Scope scope, int a, int b, int c) { /** * The TIPC visibility scope. - * + * * @author Christian Kohlschütter */ @NonNullByDefault @@ -249,7 +250,7 @@ private Scope(String name, int id) { /** * Returns a {@link Scope} instance given an integer value. - * + * * @param v The scope value. * @return The {@link Scope} instance. */ @@ -258,11 +259,17 @@ public static Scope ofValue(int v) { } } - private AFTIPCSocketAddress(int port, final byte[] socketAddress, ByteBuffer nativeAddress) + private AFTIPCSocketAddress(int port, final byte[] socketAddress, Lease nativeAddress) throws SocketException { super(port, socketAddress, nativeAddress, addressFamily()); } + private static AFTIPCSocketAddress newAFSocketAddress(int port, final byte[] socketAddress, + Lease nativeAddress) throws SocketException { + return newDeserializedAFSocketAddress(port, socketAddress, nativeAddress, addressFamily(), + AFTIPCSocketAddress::new); + } + /** * Returns an {@link AFTIPCSocketAddress} that refers to a given service type and instance, using * the given scope. @@ -294,7 +301,7 @@ public static AFTIPCSocketAddress ofService(int type, int instance) throws Socke /** * Returns an {@link AFTIPCSocketAddress} that refers to a given service type and instance, using * the given scope and the given lookup domain. - * + * * @param scope The address scope. * @param type The service type (0-63 are reserved). * @param instance The service instance ID. @@ -425,16 +432,16 @@ public static AFTIPCSocketAddress ofTopologyService() throws SocketException { private static int parseUnsignedInt(String v) { if (v.startsWith("0x")) { - return Integer.parseUnsignedInt(v.substring(2), 16); + return parseUnsignedInt(v.substring(2), 16); } else { - return Integer.parseUnsignedInt(v); + return parseUnsignedInt(v, 10); } } /** * Returns an {@link AFTIPCSocketAddress} given a special {@link InetAddress} that encodes the * byte sequence of an AF_TIPC socket address, like those returned by {@link #wrapAddress()}. - * + * * @param address The "special" {@link InetAddress}. * @param port The port (use 0 for "none"). * @return The {@link AFTIPCSocketAddress} instance. @@ -449,7 +456,7 @@ public static AFTIPCSocketAddress unwrap(InetAddress address, int port) throws S * Returns an {@link AFTIPCSocketAddress} given a special {@link InetAddress} hostname that * encodes the byte sequence of an AF_TIPC socket address, like those returned by * {@link #wrapAddress()}. - * + * * @param hostname The "special" hostname, as provided by {@link InetAddress#getHostName()}. * @param port The port (use 0 for "none"). * @return The {@link AFTIPCSocketAddress} instance. @@ -462,7 +469,7 @@ public static AFTIPCSocketAddress unwrap(String hostname, int port) throws Socke /** * Returns an {@link AFTIPCSocketAddress} given a generic {@link SocketAddress}. - * + * * @param address The address to unwrap. * @return The {@link AFTIPCSocketAddress} instance. * @throws SocketException if the operation fails, for example when an unsupported address is @@ -478,7 +485,7 @@ public static AFTIPCSocketAddress unwrap(SocketAddress address) throws SocketExc /** * Returns the scope of this address. - * + * * @return The scope. */ public Scope getScope() { @@ -491,7 +498,7 @@ public Scope getScope() { /** * Returns the TIPC type part of this address. - * + * * @return The type identifier */ public int getTIPCType() { @@ -502,7 +509,7 @@ public int getTIPCType() { /** * Returns the TIPC instance part of this address. - * + * * @return The instance identifier. */ public int getTIPCInstance() { @@ -513,7 +520,7 @@ public int getTIPCInstance() { /** * Returns the TIPC domain part of this address. - * + * * @return The domain identifier. */ public int getTIPCDomain() { @@ -524,7 +531,7 @@ public int getTIPCDomain() { /** * Returns the TIPC lower instance of this address. - * + * * @return The lower instance identifier. */ public int getTIPCLower() { @@ -535,7 +542,7 @@ public int getTIPCLower() { /** * Returns the TIPC upper instance of this address. - * + * * @return The lower instance identifier. */ public int getTIPCUpper() { @@ -546,7 +553,7 @@ public int getTIPCUpper() { /** * Returns the TIPC ref of this address. - * + * * @return The ref identifier. */ public int getTIPCRef() { @@ -557,7 +564,7 @@ public int getTIPCRef() { /** * Returns the TIPC node hash of this address. - * + * * @return The node hash. */ public int getTIPCNodeHash() { @@ -602,7 +609,7 @@ public File getFile() throws FileNotFoundException { /** * Checks if an {@link InetAddress} can be unwrapped to an {@link AFTIPCSocketAddress}. - * + * * @param addr The instance to check. * @return {@code true} if so. * @see #wrapAddress() @@ -614,7 +621,7 @@ public static boolean isSupportedAddress(InetAddress addr) { /** * Checks if a {@link SocketAddress} can be unwrapped to an {@link AFTIPCSocketAddress}. - * + * * @param addr The instance to check. * @return {@code true} if so. * @see #unwrap(InetAddress, int) @@ -636,7 +643,7 @@ private static byte[] toBytes(AddressType addrType, Scope scope, int a, int b, i /** * Returns the corresponding {@link AFAddressFamily}. - * + * * @return The address family instance. */ @SuppressWarnings("null") @@ -645,6 +652,10 @@ public static synchronized AFAddressFamily addressFamily() afTipc = AFAddressFamily.registerAddressFamily("tipc", // AFTIPCSocketAddress.class, new AFSocketAddressConfig() { + private final AFSocketAddressConstructor addrConstr = + isUseDeserializationForInit() ? AFTIPCSocketAddress::newAFSocketAddress + : AFTIPCSocketAddress::new; + @Override protected AFTIPCSocketAddress parseURI(URI u, int port) throws SocketException { return AFTIPCSocketAddress.of(u, port); @@ -652,7 +663,7 @@ protected AFTIPCSocketAddress parseURI(URI u, int port) throws SocketException { @Override protected AFSocketAddressConstructor addressConstructor() { - return AFTIPCSocketAddress::new; + return addrConstr; } @Override @@ -676,15 +687,15 @@ protected Set uriSchemes() { private String toTipcInt(int v) { if (v < 0) { - return "0x" + Integer.toUnsignedString(v, 16); + return "0x" + toUnsignedString(v, 16); } else { - return Integer.toUnsignedString(v); + return toUnsignedString(v); } } /** * Returns an {@link AFTIPCSocketAddress} for the given URI, if possible. - * + * * @param uri The URI. * @return The address. * @throws SocketException if the operation fails. @@ -696,7 +707,7 @@ public static AFTIPCSocketAddress of(URI uri) throws SocketException { /** * Returns an {@link AFTIPCSocketAddress} for the given URI, if possible. - * + * * @param uri The URI. * @param overridePort The port to forcibly use, or {@code -1} for "don't override". * @return The address. diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFTIPCSocketImplExtensions.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFTIPCSocketImplExtensions.java index 4a4268d55..3df08bcba 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFTIPCSocketImplExtensions.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFTIPCSocketImplExtensions.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ /** * TIPC-specific code that resides in the native library. To be used by {@code AFTIPCSocket} and * {@code AFTIPCDatagramSocket} only. - * + * * @author Christian Kohlschütter */ public final class AFTIPCSocketImplExtensions implements @@ -36,9 +36,9 @@ public final class AFTIPCSocketImplExtensions implements /** * Returns the TIPC "ErrInfo" data from the ancillary receive buffer. - * + * * Invalid for any other use. - * + * * @return The errinfo. */ public int[] getTIPCErrInfo() { @@ -47,9 +47,9 @@ public int[] getTIPCErrInfo() { /** * Returns the TIPC "DestName" data from the ancillary receive buffer. - * + * * Invalid for any other use. - * + * * @return The DestName. */ public int[] getTIPCDestName() { @@ -58,7 +58,7 @@ public int[] getTIPCDestName() { /** * Retrieves the 16-byte TIPC node identity given a node hash. - * + * * @param peer The node hash. * @return The node identity, or {@code null} if unsupported. * @throws IOException on error. @@ -69,7 +69,7 @@ public byte[] getTIPCNodeId(int peer) throws IOException { /** * Retrieves the TIPC link name given a node hash and bearer Id. - * + * * @param peer The node hash. * @param bearerId The bearer Id. * @return The link name, or {@code null} if unsupported. diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXDatagramChannel.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXDatagramChannel.java index 79e2369d4..226245019 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXDatagramChannel.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXDatagramChannel.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ /** * A {@link DatagramChannel} implementation that works with AF_UNIX Unix domain sockets. - * + * * @author Christian Kohlschütter */ public final class AFUNIXDatagramChannel extends AFDatagramChannel implements diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXDatagramSocket.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXDatagramSocket.java index ab3329d5e..27a3ca1f4 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXDatagramSocket.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXDatagramSocket.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ /** * A {@link DatagramSocket} implementation that works with AF_UNIX Unix domain sockets. - * + * * @author Christian Kohlschütter */ public final class AFUNIXDatagramSocket extends AFDatagramSocket implements @@ -34,6 +34,11 @@ public final class AFUNIXDatagramSocket extends AFDatagramSocket { + return new AFUNIXDatagramSocket(fd, socketType); + }); + } + static AFUNIXDatagramSocket newInstance(FileDescriptor fdObj, int localPort, int remotePort) throws IOException { return (AFUNIXDatagramSocket) newInstance(AFUNIXDatagramSocket::new, fdObj, localPort, @@ -90,4 +108,9 @@ public AFUNIXSocketCredentials getPeerCredentials() throws IOException { } return ((AFUNIXDatagramSocketImpl) getAFImpl()).getPeerCredentials(); } + + @Override + protected AFDatagramSocket newDatagramSocketInstance() throws IOException { + return new AFUNIXDatagramSocket(null); + } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXDatagramSocketImpl.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXDatagramSocketImpl.java index 4afca7d5c..e8e2c4d75 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXDatagramSocketImpl.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXDatagramSocketImpl.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,11 @@ final class AFUNIXDatagramSocketImpl extends AFDatagramSocketImpl { AFUNIXDatagramSocketImpl(FileDescriptor fd) throws IOException { - super(AFUNIXSocketAddress.AF_UNIX, fd, AFSocketType.SOCK_DGRAM); + this(fd, AFSocketType.SOCK_DGRAM); + } + + AFUNIXDatagramSocketImpl(FileDescriptor fd, AFSocketType socketType) throws IOException { + super(AFUNIXSocketAddress.AF_UNIX, fd, socketType); } AFUNIXSocketCredentials getPeerCredentials() throws IOException { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXProtocolFamily.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXProtocolFamily.java index 5e8411bb2..14705b643 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXProtocolFamily.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXProtocolFamily.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,16 +17,31 @@ */ package org.newsclub.net.unix; -import java.net.ProtocolFamily; +import java.io.IOException; /** * Describes the protocol family supported by {@link AFUNIXSocketAddress} etc. - * + * * @author Christian Kohlschütter */ -public enum AFUNIXProtocolFamily implements ProtocolFamily { +public enum AFUNIXProtocolFamily implements AFProtocolFamily { /** * UNIX domain. */ UNIX; + + @Override + public AFDatagramChannel openDatagramChannel() throws IOException { + return AFUNIXDatagramChannel.open(); + } + + @Override + public AFServerSocketChannel openServerSocketChannel() throws IOException { + return AFUNIXServerSocketChannel.open(); + } + + @Override + public AFSocketChannel openSocketChannel() throws IOException { + return AFUNIXSocketChannel.open(); + } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSelectorProvider.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSelectorProvider.java index 9dbf8344c..91a87f342 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSelectorProvider.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSelectorProvider.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -88,7 +88,7 @@ private AFUNIXSelectorProvider() { /** * Returns the singleton instance. - * + * * @return The instance. */ @SuppressFBWarnings("MS_EXPOSE_REP") @@ -98,7 +98,7 @@ public static AFUNIXSelectorProvider getInstance() { /** * Returns the singleton instance. - * + * * @return The instance. */ public static AFUNIXSelectorProvider provider() { @@ -107,7 +107,7 @@ public static AFUNIXSelectorProvider provider() { @Override protected

    AFSocketPair

    newSocketPair(P s1, P s2) { - return new AFUNIXSocketPair

    (s1, s2); + return new AFUNIXSocketPair<>(s1, s2); } @SuppressWarnings("unchecked") @@ -122,6 +122,13 @@ public AFUNIXSocketPair openDatagramChannelPair() throws return (AFUNIXSocketPair) super.openDatagramChannelPair(); } + @SuppressWarnings("unchecked") + @Override + public AFUNIXSocketPair openDatagramChannelPair(AFSocketType type) + throws IOException { + return (AFUNIXSocketPair) super.openDatagramChannelPair(type); + } + @Override protected AFUNIXSocket newSocket() throws IOException { return AFUNIXSocket.newInstance(); @@ -132,6 +139,11 @@ public AFUNIXDatagramChannel openDatagramChannel() throws IOException { return AFUNIXDatagramSocket.newInstance().getChannel(); } + @Override + public AFUNIXDatagramChannel openDatagramChannel(AFSocketType type) throws IOException { + return AFUNIXDatagramSocket.newInstance(type).getChannel(); + } + @Override public AFUNIXDatagramChannel openDatagramChannel(ProtocolFamily family) throws IOException { return (AFUNIXDatagramChannel) super.openDatagramChannel(family); diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXServerSocket.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXServerSocket.java index d2d75fe1b..785641d83 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXServerSocket.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXServerSocket.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,13 +26,13 @@ /** * The server part of an AF_UNIX domain socket. - * + * * @author Christian Kohlschütter */ -public class AFUNIXServerSocket extends AFServerSocket { +public final class AFUNIXServerSocket extends AFServerSocket { /** * Constructs a new, unconnected instance. - * + * * @throws IOException if the operation fails. */ protected AFUNIXServerSocket() throws IOException { @@ -41,7 +41,7 @@ protected AFUNIXServerSocket() throws IOException { /** * Constructs a new instance, optionally associated with the given file descriptor. - * + * * @param fdObj The file descriptor, or {@code null}. * @throws IOException if the operation fails. */ @@ -61,7 +61,7 @@ public AFUNIXServerSocketChannel getChannel() { /** * Returns a new, unbound AF_UNIX {@link ServerSocket}. - * + * * @return The new, unbound {@link AFServerSocket}. * @throws IOException if the operation fails. */ @@ -78,7 +78,7 @@ static AFUNIXServerSocket newInstance(FileDescriptor fdObj, int localPort, int r /** * Returns a new AF_UNIX {@link ServerSocket} that is bound to the given * {@link AFUNIXSocketAddress}. - * + * * @param addr The socket file to bind to. * @return The new, bound {@link AFServerSocket}. * @throws IOException if the operation fails. @@ -89,7 +89,7 @@ public static AFUNIXServerSocket bindOn(final AFUNIXSocketAddress addr) throws I /** * Returns a new AF_UNIX {@link ServerSocket} that is bound to the given {@link AFSocketAddress}. - * + * * @param addr The socket file to bind to. * @param deleteOnClose If {@code true}, the socket file (if the address points to a file) will be * deleted upon {@link #close}. @@ -103,7 +103,7 @@ public static AFUNIXServerSocket bindOn(final AFUNIXSocketAddress addr, boolean /** * Returns a new AF_UNIX {@link ServerSocket} that is bound to the given path. - * + * * @param path The path to bind to. * @param deleteOnClose If {@code true}, the socket file will be deleted upon {@link #close}. * @return The new, bound {@link AFServerSocket}. @@ -116,7 +116,7 @@ public static AFUNIXServerSocket bindOn(final File path, boolean deleteOnClose) /** * Returns a new AF_UNIX {@link ServerSocket} that is bound to the given path. - * + * * @param path The path to bind to. * @param deleteOnClose If {@code true}, the socket file will be deleted upon {@link #close}. * @return The new, bound {@link AFServerSocket}. @@ -131,7 +131,7 @@ public static AFUNIXServerSocket bindOn(final Path path, boolean deleteOnClose) /** * Returns a new, unbound AF_UNIX {@link ServerSocket} that will always bind to the given * address, regardless of any socket address used in a call to bind. - * + * * @param forceAddr The address to use. * @return The new, yet unbound {@link AFServerSocket}. * @throws IOException if an exception occurs. @@ -142,13 +142,13 @@ public static AFUNIXServerSocket forceBindOn(final AFUNIXSocketAddress forceAddr } @Override - protected AFUNIXSocketImpl newImpl(FileDescriptor fdObj) throws SocketException { + protected AFSocketImpl newImpl(FileDescriptor fdObj) throws SocketException { return new AFUNIXSocketImpl(fdObj); } /** * Returns a new {@link AFSocket} instance. - * + * * @return The new instance. * @throws IOException on error. */ diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXServerSocketChannel.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXServerSocketChannel.java index be58f5b93..f76608bf7 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXServerSocketChannel.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXServerSocketChannel.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ /** * A selectable channel for stream-oriented listening sockets. - * + * * @author Christian Kohlschütter */ public final class AFUNIXServerSocketChannel extends AFServerSocketChannel { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocket.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocket.java index a4313402e..1e67c2a58 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocket.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocket.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,16 +17,22 @@ */ package org.newsclub.net.unix; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.FileDescriptor; import java.io.IOException; import java.net.Socket; import java.net.SocketException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.SocketChannel; +import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jdt.annotation.NonNull; /** * Implementation of an AF_UNIX domain socket. - * + * * @author Christian Kohlschütter */ public final class AFUNIXSocket extends AFSocket implements @@ -58,12 +64,12 @@ protected AFUNIXSocketChannel newChannel() { /** * Creates a new, unbound {@link AFSocket}. - * + * * This "default" implementation is a bit "lenient" with respect to the specification. - * + * * In particular, we ignore calls to {@link Socket#getTcpNoDelay()} and * {@link Socket#setTcpNoDelay(boolean)}. - * + * * @return A new, unbound socket. * @throws IOException if the operation fails. */ @@ -87,10 +93,10 @@ static AFUNIXSocket newInstance(AFUNIXSocketFactory factory) throws SocketExcept /** * Creates a new, unbound, "strict" {@link AFSocket}. - * + * * This call uses an implementation that tries to be closer to the specification than * {@link #newInstance()}, at least for some cases. - * + * * @return A new, unbound socket. * @throws IOException if the operation fails. */ @@ -100,7 +106,7 @@ public static AFUNIXSocket newStrictInstance() throws IOException { /** * Creates a new {@link AFSocket} and connects it to the given {@link AFUNIXSocketAddress}. - * + * * @param addr The address to connect to. * @return A new, connected socket. * @throws IOException if the operation fails. @@ -147,14 +153,14 @@ public boolean hasOutboundFileDescriptors() { /** * Returns true iff {@link AFUNIXSocket}s are supported by the current Java VM. - * + * * To support {@link AFSocket}s, a custom JNI library must be loaded that is supplied with * junixsocket, and the system must support AF_UNIX sockets. - * + * * This call is equivalent to checking {@link AFSocket#isSupported()} and * {@link AFSocket#supports(AFSocketCapability)} with * {@link AFSocketCapability#CAPABILITY_UNIX_DOMAIN}. - * + * * @return {@code true} iff supported. */ public static boolean isSupported() { @@ -163,13 +169,14 @@ public static boolean isSupported() { /** * Very basic self-test function. - * + * * Prints "supported" and "capabilities" status to System.out. - * + * * @param args ignored. */ public static void main(String[] args) { - // If you want to run this directly from within Eclipse, see AFUNIXSocketTest#testMain. + // If you want to run this directly from within Eclipse, see + // org.newsclub.net.unix.domain.SocketTest#testMain. System.out.print(AFUNIXSocket.class.getName() + ".isSupported(): "); System.out.flush(); System.out.println(AFUNIXSocket.isSupported()); @@ -179,5 +186,75 @@ public static void main(String[] args) { System.out.flush(); System.out.println(AFSocket.supports(cap)); } + System.out.println(); + if (AFSocket.supports(AFSocketCapability.CAPABILITY_UNIX_DOMAIN)) { + System.out.println("Starting mini selftest..."); + miniSelftest(); + } else { + System.out.println( + "Skipping mini selftest; AFSocketCapability.CAPABILITY_UNIX_DOMAIN is missing"); + } + } + + private static void miniSelftest() { + AtomicBoolean success = new AtomicBoolean(true); + try { + AFUNIXSocketAddress addr = AFUNIXSocketAddress.ofNewTempFile(); + System.out.println("Using temporary address: " + addr); + try (AFUNIXServerSocket server = addr.newBoundServerSocket()) { + Thread t = new Thread(() -> { + try { + try (AFUNIXSocket client = server.accept()) { // NOPMD.UseTryWithResources + System.out.println("Server accepted client connection"); + try (SocketChannel chann = client.getChannel()) { + ByteBuffer bb = ByteBuffer.allocate(64).order(ByteOrder.BIG_ENDIAN); + + int numRead = 0; + while (bb.position() != 4 && numRead != -1) { + numRead = chann.read(bb); + } + if (bb.position() != 4) { + throw new IOException("Unexpected number of bytes read: " + bb.position()); + } + bb.flip(); + int v; + if ((v = bb.getInt()) != 0xABCDEF12) { + throw new IOException("Received unexpected data from client: 0x" + Integer + .toHexString(v)); + } + bb.clear(); + bb.putLong(0x00112233456789L); + bb.flip(); + chann.write(bb); + } + } finally { + server.close(); // NOPMD + } + } catch (Exception e) { // NOPMD + success.set(false); + e.printStackTrace(); + } + }); + t.start(); + + try (AFUNIXSocket socket = addr.newConnectedSocket(); + DataInputStream in = new DataInputStream(socket.getInputStream()); + DataOutputStream out = new DataOutputStream(socket.getOutputStream());) { + out.writeInt(0xABCDEF12); + out.flush(); + long v = in.readLong(); + if (v != 0x00112233456789L) { + throw new IOException("Received unexpected data from server: 0x" + Long.toHexString(v)); + } + } + System.out.println("Data exchange succeeded"); + } + } catch (Exception e) { // NOPMD + success.set(false); + e.printStackTrace(); + return; + } finally { + System.out.println("mini selftest " + (success.get() ? "passed" : "failed")); + } } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketAddress.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketAddress.java index 4325b2a56..8b1d073bf 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketAddress.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketAddress.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,18 +37,19 @@ import java.util.Set; import org.eclipse.jdt.annotation.NonNull; +import org.newsclub.net.unix.pool.ObjectPool.Lease; /** * Describes an {@link InetSocketAddress} that actually uses AF_UNIX sockets instead of AF_INET. - * + * * The ability to specify a port number is not specified by AF_UNIX sockets, but we need it * sometimes, for example for RMI-over-AF_UNIX. - * + * * @author Christian Kohlschütter */ @SuppressWarnings("PMD.ShortMethodName") public final class AFUNIXSocketAddress extends AFSocketAddress { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; // do not change! private static final Charset ADDRESS_CHARSET = Charset.defaultCharset(); @@ -57,6 +58,10 @@ public final class AFUNIXSocketAddress extends AFSocketAddress { .registerAddressFamily("un", // AFUNIXSocketAddress.class, new AFSocketAddressConfig() { + private final AFSocketAddressConstructor addrConstr = + isUseDeserializationForInit() ? AFUNIXSocketAddress::newAFSocketAddress + : AFUNIXSocketAddress::new; + @Override public AFUNIXSocketAddress parseURI(URI u, int port) throws SocketException { return AFUNIXSocketAddress.of(u, port); @@ -64,7 +69,7 @@ public AFUNIXSocketAddress parseURI(URI u, int port) throws SocketException { @Override protected AFSocketAddressConstructor addressConstructor() { - return AFUNIXSocketAddress::new; + return addrConstr; } @Override @@ -76,9 +81,14 @@ protected String selectorProviderClassname() { protected Set uriSchemes() { return new HashSet<>(Arrays.asList("unix", "http+unix", "https+unix")); } + + @Override + protected SocketAddress nullBindAddress() throws IOException { + return AFUNIXSocketAddress.ofNewTempFile(); + } }); - private AFUNIXSocketAddress(int port, final byte[] socketAddress, ByteBuffer nativeAddress) + private AFUNIXSocketAddress(int port, final byte[] socketAddress, Lease nativeAddress) throws SocketException { super(port, socketAddress, nativeAddress, AF_UNIX); } @@ -86,12 +96,13 @@ private AFUNIXSocketAddress(int port, final byte[] socketAddress, ByteBuffer nat /** * Returns an {@link AFUNIXSocketAddress} that points to the AF_UNIX socket specified by the given * file and port. Legacy constructor, do not use! - * + * * @param socketFile The socket to connect to. * @throws SocketException if the operation fails. * @deprecated Use {@link #of(File)} instead. * @see #of(File) */ + @Deprecated public AFUNIXSocketAddress(File socketFile) throws SocketException { this(socketFile, 0); } @@ -99,22 +110,29 @@ public AFUNIXSocketAddress(File socketFile) throws SocketException { /** * Returns an {@link AFUNIXSocketAddress} that points to the AF_UNIX socket specified by the given * file. Legacy constructor, do not use! - * + * * @param socketFile The socket to connect to. * @param port The port associated with this socket, or {@code 0} when no port should be assigned. * @throws SocketException if the operation fails. * @deprecated Use {@link #of(File, int)} instead. * @see #of(File, int) */ + @Deprecated public AFUNIXSocketAddress(File socketFile, int port) throws SocketException { this(port, of(socketFile, port).getPathAsBytes(), of(socketFile, port) .getNativeAddressDirectBuffer()); } + static AFUNIXSocketAddress newAFSocketAddress(int port, final byte[] socketAddress, + Lease nativeAddress) throws SocketException { + return newDeserializedAFSocketAddress(port, socketAddress, nativeAddress, AF_UNIX, + AFUNIXSocketAddress::new); + } + /** * Returns an {@link AFUNIXSocketAddress} that points to the AF_UNIX socket specified by the given * file. - * + * * @param socketFile The socket to connect to. * @return A corresponding {@link AFUNIXSocketAddress} instance. * @throws SocketException if the operation fails. @@ -126,7 +144,7 @@ public static AFUNIXSocketAddress of(final File socketFile) throws SocketExcepti /** * Returns an {@link AFUNIXSocketAddress} that points to the AF_UNIX socket specified by the given * file, assigning the given port to it. - * + * * @param socketFile The socket to connect to. * @param port The port associated with this socket, or {@code 0} when no port should be assigned. * @return A corresponding {@link AFUNIXSocketAddress} instance. @@ -139,10 +157,10 @@ public static AFUNIXSocketAddress of(final File socketFile, int port) throws Soc /** * Returns an {@link AFUNIXSocketAddress} that points to the AF_UNIX socket specified by the given * byte sequence. - * + * * NOTE: By specifying a byte array that starts with a zero byte, you indicate that the abstract * namespace is to be used. This feature is not available on all target platforms. - * + * * @param socketAddress The socket address (as bytes). * @return A corresponding {@link AFUNIXSocketAddress} instance. * @throws SocketException if the operation fails. @@ -155,7 +173,7 @@ public static AFUNIXSocketAddress of(final byte[] socketAddress) throws SocketEx /** * Returns an {@link AFUNIXSocketAddress} that points to the AF_UNIX socket specified by the given * byte sequence, assigning the given port to it. - * + * * NOTE: By specifying a byte array that starts with a zero byte, you indicate that the abstract * namespace is to be used. This feature is not available on all target platforms. * @@ -173,31 +191,35 @@ public static AFUNIXSocketAddress of(final byte[] socketAddress, int port) /** * Returns an {@link AFUNIXSocketAddress} that points to the AF_UNIX socket specified by the given * path. - * + * * @param socketPath The socket to connect to. * @return A corresponding {@link AFUNIXSocketAddress} instance. * @throws SocketException if the operation fails. */ - public static AFUNIXSocketAddress of(final Path socketPath) throws SocketException { + public static AFUNIXSocketAddress of(Path socketPath) throws SocketException { return of(socketPath, 0); } /** * Returns an {@link AFUNIXSocketAddress} that points to the AF_UNIX socket specified by the given * path, assigning the given port to it. - * + * * @param socketPath The socket to connect to. * @param port The port associated with this socket, or {@code 0} when no port should be assigned. * @return A corresponding {@link AFUNIXSocketAddress} instance. * @throws SocketException if the operation fails. */ - public static AFUNIXSocketAddress of(final Path socketPath, int port) throws SocketException { + public static AFUNIXSocketAddress of(Path socketPath, int port) throws SocketException { + if (!PathUtil.isPathInDefaultFileSystem(socketPath)) { + throw new SocketException("Path is not in the default file system"); + } + return of(socketPath.toString().getBytes(ADDRESS_CHARSET), port); } /** * Returns an {@link AFUNIXSocketAddress} for the given URI, if possible. - * + * * @param u The URI. * @return The address. * @throws SocketException if the operation fails. @@ -208,7 +230,7 @@ public static AFUNIXSocketAddress of(URI u) throws SocketException { /** * Returns an {@link AFUNIXSocketAddress} for the given URI, if possible. - * + * * @param u The URI. * @param overridePort The port to forcibly use, or {@code -1} for "don't override". * @return The address. @@ -240,7 +262,7 @@ public static AFUNIXSocketAddress of(URI u, int overridePort) throws SocketExcep /** * Returns an {@link AFUNIXSocketAddress} that points to a temporary, non-existent but accessible * path in the file system. - * + * * @return A corresponding {@link AFUNIXSocketAddress} instance. * @throws IOException if the operation fails. */ @@ -251,7 +273,7 @@ public static AFUNIXSocketAddress ofNewTempFile() throws IOException { /** * Returns an {@link AFUNIXSocketAddress} that points to a temporary, non-existent but accessible * path in the file system, assigning the given port to it. - * + * * @param port The port associated with this socket, or {@code 0} when no port should be assigned. * @return A corresponding {@link AFUNIXSocketAddress} instance. * @throws IOException if the operation fails. @@ -260,6 +282,24 @@ public static AFUNIXSocketAddress ofNewTempPath(int port) throws IOException { return of(newTempPath(true), port); } + /** + * Returns an {@link AFUNIXSocketAddress} based on the given {@link SocketAddress}. + * + * This either simply casts an existing {@link AFUNIXSocketAddress}, or converts a + * {@code UnixDomainSocketAddress} to it. + * + * @param address The address to convert. + * @return A corresponding {@link AFUNIXSocketAddress} instance. + * @throws SocketException if the operation fails. + */ + public static AFUNIXSocketAddress of(SocketAddress address) throws IOException { + AFUNIXSocketAddress addr = unwrap(Objects.requireNonNull(address)); + if (addr == null) { + throw new SocketException("Could not convert SocketAddress to AFUNIXSocketAddress"); + } + return addr; + } + static File newTempPath(boolean deleteOnExit) throws IOException { File f = File.createTempFile("jux", ".sock"); if (deleteOnExit) { @@ -274,7 +314,7 @@ static File newTempPath(boolean deleteOnExit) throws IOException { /** * Returns an {@link AFUNIXSocketAddress} given a special {@link InetAddress} that encodes the * byte sequence of an AF_UNIX socket address, like those returned by {@link #wrapAddress()}. - * + * * @param address The "special" {@link InetAddress}. * @param port The port (use 0 for "none"). * @return The {@link AFUNIXSocketAddress} instance. @@ -287,26 +327,26 @@ public static AFUNIXSocketAddress unwrap(InetAddress address, int port) throws S /** * Returns an {@link AFUNIXSocketAddress} given a generic {@link SocketAddress}. - * + * * @param address The address to unwrap. * @return The {@link AFUNIXSocketAddress} instance. * @throws SocketException if the operation fails, for example when an unsupported address is * specified. */ public static AFUNIXSocketAddress unwrap(SocketAddress address) throws SocketException { - // FIXME: add support for UnixDomainSocketAddress Objects.requireNonNull(address); - if (!isSupportedAddress(address)) { + AFSupplier supplier = supportedAddressSupplier(address); + if (supplier == null) { throw new SocketException("Unsupported address"); } - return (AFUNIXSocketAddress) address; + return supplier.get(); } /** * Returns an {@link AFUNIXSocketAddress} given a special {@link InetAddress} hostname that * encodes the byte sequence of an AF_UNIX socket address, like those returned by * {@link #wrapAddress()}. - * + * * @param hostname The "special" hostname, as provided by {@link InetAddress#getHostName()}. * @param port The port (use 0 for "none"). * @return The {@link AFUNIXSocketAddress} instance. @@ -319,11 +359,11 @@ public static AFUNIXSocketAddress unwrap(String hostname, int port) throws Socke /** * Convenience method to create an {@link AFUNIXSocketAddress} in the abstract namespace. - * + * * The returned socket address will use the byte representation of this identifier (using the * system's default character encoding), prefixed with a null byte (to indicate the abstract * namespace is used). - * + * * @param name The identifier in the abstract namespace, without trailing zero or @. * @return The address. * @throws SocketException if the operation fails. @@ -334,11 +374,11 @@ public static AFUNIXSocketAddress inAbstractNamespace(String name) throws Socket /** * Convenience method to create an {@link AFUNIXSocketAddress} in the abstract namespace. - * + * * The returned socket address will use the byte representation of this identifier (using the * system's default character encoding), prefixed with a null byte (to indicate the abstract * namespace is used). - * + * * @param name The identifier in the abstract namespace, without trailing zero or @. * @param port The port associated with this socket, or {@code 0} when no port should be assigned. * @return The address. @@ -380,10 +420,10 @@ public String toString() { /** * Returns the path to the UNIX domain socket, as a human-readable string using the default * encoding. - * + * * For addresses in the abstract namespace, the US_ASCII encoding is used; zero-bytes are * converted to '@', other non-printable bytes are converted to '.' - * + * * @return The path. * @see #getPathAsBytes() */ @@ -411,10 +451,10 @@ public String getPath() { /** * Returns the {@link Charset} used to encode/decode {@link AFUNIXSocketAddress}es. - * + * * This is usually the system default charset, unless that is {@link StandardCharsets#US_ASCII} * (7-bit), in which case {@link StandardCharsets#ISO_8859_1} is used instead. - * + * * @return The charset. */ public static Charset addressCharset() { @@ -423,7 +463,7 @@ public static Charset addressCharset() { /** * Returns the path to the UNIX domain socket, as bytes. - * + * * @return The path. * @see #getPath() */ @@ -432,8 +472,9 @@ public byte[] getPathAsBytes() { } /** - * Checks if the address is in the abstract namespace. - * + * Checks if the address is in the abstract namespace (or, for Haiku OS, in the internal + * namespace). + * * @return {@code true} if the address is in the abstract namespace. */ public boolean isInAbstractNamespace() { @@ -462,7 +503,7 @@ public File getFile() throws FileNotFoundException { /** * Checks if an {@link InetAddress} can be unwrapped to an {@link AFUNIXSocketAddress}. - * + * * @param addr The instance to check. * @return {@code true} if so. * @see #wrapAddress() @@ -474,18 +515,35 @@ public static boolean isSupportedAddress(InetAddress addr) { /** * Checks if a {@link SocketAddress} can be unwrapped to an {@link AFUNIXSocketAddress}. - * + * * @param addr The instance to check. * @return {@code true} if so. * @see #unwrap(InetAddress, int) */ public static boolean isSupportedAddress(SocketAddress addr) { - return (addr instanceof AFUNIXSocketAddress); + return supportedAddressSupplier(addr) != null; + } + + /** + * Checks if the given address can be unwrapped to an {@link AFUNIXSocketAddress}, and if so, + * returns a supplier function; if not, {@code null} is returned. + * + * @param addr The address. + * @return The supplier, or {@code null}. + */ + static AFSupplier supportedAddressSupplier(SocketAddress addr) { + if (addr == null) { + return null; + } else if (addr instanceof AFUNIXSocketAddress) { + return () -> ((AFUNIXSocketAddress) addr); + } else { + return SocketAddressUtil.supplyAFUNIXSocketAddress(addr); + } } /** * Returns the corresponding {@link AFAddressFamily}. - * + * * @return The address family instance. */ @SuppressWarnings("null") diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketCapability.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketCapability.java index 8e56621a8..89e1e086c 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketCapability.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketCapability.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,12 +20,12 @@ /** * Describes junixsocket capabilities the current environment (system platform, native library, * etc.) may or may not support. - * + * * You can check whether your environment supports a given capability by calling * {@link AFUNIXSocket#supports(AFUNIXSocketCapability)}. - * + * * This enum is deprecated. Use {@link AFSocketCapability} instead. - * + * * @see AFSocketCapability */ @Deprecated @@ -49,7 +49,7 @@ public enum AFUNIXSocketCapability { /** * A pair of interconnected sockets can be created natively. - * + * * This currently not possible on Windows, but instead emulated via anonymous AF_INET ports when * you use {@link AFUNIXSocketPair}. */ diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketChannel.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketChannel.java index a5f27583f..e74453679 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketChannel.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketChannel.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ /** * A selectable channel for stream-oriented connecting sockets. - * + * * @author Christian Kohlschütter */ public final class AFUNIXSocketChannel extends AFSocketChannel implements diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketCredentials.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketCredentials.java index 28b7f7c93..5af810b17 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketCredentials.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketCredentials.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,7 +63,7 @@ public final class AFUNIXSocketCredentials implements Serializable { /** * Returns the "pid" (process ID), or {@code -1} if it could not be retrieved. - * + * * @return The pid, or -1. */ public long getPid() { @@ -72,7 +72,7 @@ public long getPid() { /** * Returns the "uid" (user ID), or {@code -1} if it could not be retrieved. - * + * * @return The uid, or -1. */ public long getUid() { @@ -81,7 +81,7 @@ public long getUid() { /** * Returns the primary "gid" (group ID), or {@code -1} if it could not be retrieved. - * + * * @return The gid, or -1. */ public long getGid() { @@ -90,10 +90,10 @@ public long getGid() { /** * Returns all "gid" values (group IDs), or {@code null} if they could not be retrieved. - * + * * Note that this list may be incomplete (only the primary gid may be returned), but it is * guaranteed that the first one in the list is the primary gid as returned by {@link #getGid()}. - * + * * @return The gids, or null. */ public long[] getGids() { @@ -103,7 +103,7 @@ public long[] getGids() { /** * Returns the process' unique identifier, or {@code null} if no such identifier could be * retrieved. Note that all processes run by the same Java runtime may share the same UUID. - * + * * @return The UUID, or null. */ public UUID getUUID() { @@ -120,7 +120,7 @@ void setGids(long[] gids) { /** * Checks if neither of the possible peer credentials are set. - * + * * @return {@code true} if no credentials set. */ public boolean isEmpty() { @@ -201,10 +201,10 @@ public boolean equals(Object obj) { /** * Returns the {@link AFUNIXSocketCredentials} for the currently active remote session, or * {@code null} if it was not possible to retrieve these credentials. - * + * * NOTE: For now, only RMI remote sessions are supported (RemoteServer sessions during a remote * method invocation). - * + * * If you want to retrieve the peer credentials for an RMI server, see junixsocket-rmi's * RemotePeerInfo. * diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketExtensions.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketExtensions.java index a59ea780d..d307ad1bc 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketExtensions.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketExtensions.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,23 +24,23 @@ /** * Defines certain methods that all junixsocket AF_UNIX socket implementations share and extend * beyond the standard socket API. - * + * * The set of features include methods to support working with ancillary messages (such as file * descriptors) as well as socket credentials. - * + * * Keep in mind that the platform this code runs on may not support these features, and exceptions * may be thrown when not checking for the corresponding {@link AFUNIXSocketCapability} first. - * + * * @author Christian Kohlschütter */ public interface AFUNIXSocketExtensions extends AFSocketExtensions { /** * Retrieves an array of incoming {@link FileDescriptor}s that were sent as ancillary messages, * along with a call to {@link InputStream#read()}, etc. - * + * * NOTE: Another call to this method will not return the same file descriptors again (most likely, * an empty array will be returned). - * + * * @return The file descriptors, or an empty array if none were available. * @throws IOException if the operation fails. */ @@ -54,15 +54,15 @@ public interface AFUNIXSocketExtensions extends AFSocketExtensions { /** * Sets a list of {@link FileDescriptor}s that should be sent as an ancillary message along with * the next write. - * + * * Important: There can only be one set of file descriptors active until the write completes. The * socket also needs to be connected for this operation to succeed. - * + * * It is also important to know that there may be an upper limit imposed by the operation system * as to how many file descriptors can be sent at once. Linux, for example, may support up to 253. * If the number of file descriptors exceeds the limit, an exception may be thrown when sending * data along with the ancillary message containing the file descriptors. - * + * * @param fdescs The file descriptors, or {@code null} if none. * @throws IOException if the operation fails. */ @@ -71,7 +71,7 @@ public interface AFUNIXSocketExtensions extends AFSocketExtensions { /** * Returns {@code true} if there are pending file descriptors to be sent as part of an ancillary * message. - * + * * @return {@code true} if there are file descriptors pending. */ boolean hasOutboundFileDescriptors(); @@ -80,7 +80,7 @@ public interface AFUNIXSocketExtensions extends AFSocketExtensions { * Retrieves the "peer credentials" for this connection. * * These credentials may be useful to authenticate the other end of the socket (client or server). - * + * * Depending on the socket/connection/environment, you may not receive any or all credentials. For * example, on Linux, {@link AFUNIXDatagramSocket} and {@link AFUNIXDatagramChannel} may not be * able to retrieve credentials at all. diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketFactory.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketFactory.java index 3ac279104..3dde06a04 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketFactory.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketFactory.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,10 +28,10 @@ /** * The base for a SocketFactory that connects to UNIX sockets. - * + * * Typically, the "hostname" is used as a reference to a socketFile on the file system. The actual * mapping is left to the implementor. - * + * * @see AFUNIXSocketFactory.FactoryArg * @see AFUNIXSocketFactory.SystemProperty * @see AFUNIXSocketFactory.URIScheme @@ -41,7 +41,7 @@ public abstract class AFUNIXSocketFactory extends AFSocketFactory[file:%3A%2F%2F(...)", i.e. * encoded and without the closing bracket. Since this is an invalid hostname, it will not trigger * a DNS lookup, but can still be used within a JDBC Connection URL. diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketImpl.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketImpl.java index 35934871b..4693e5caf 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketImpl.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketImpl.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,18 +23,18 @@ import java.net.SocketException; class AFUNIXSocketImpl extends AFSocketImpl { - protected AFUNIXSocketImpl(FileDescriptor fdObj) throws SocketException { + protected AFUNIXSocketImpl(FileDescriptor fdObj) { super(AFUNIXSocketAddress.AF_UNIX, fdObj); } /** * Changes the behavior to be somewhat lenient with respect to the specification. - * + * * In particular, we ignore calls to {@link Socket#getTcpNoDelay()} and * {@link Socket#setTcpNoDelay(boolean)}. */ static final class Lenient extends AFUNIXSocketImpl { - protected Lenient(FileDescriptor fdObj) throws SocketException { + Lenient(FileDescriptor fdObj) throws SocketException { super(fdObj); } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketPair.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketPair.java index 08e22cbee..6135019e4 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketPair.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketPair.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,11 @@ package org.newsclub.net.unix; import java.io.IOException; +import java.nio.channels.DatagramChannel; /** * A pair of sockets. - * + * * @param The socket type. * @author Christian Kohlschütter */ @@ -32,7 +33,7 @@ public final class AFUNIXSocketPair extends AFSocketPair /** * Opens a socket pair of interconnected channels. - * + * * @return The new channel pair. * @throws IOException on error. */ @@ -42,11 +43,23 @@ public static AFUNIXSocketPair open() throws IOException { /** * Opens a socket pair of interconnected datagram channels. - * + * * @return The new channel pair. * @throws IOException on error. */ public static AFUNIXSocketPair openDatagram() throws IOException { return AFUNIXSelectorProvider.provider().openDatagramChannelPair(); } + + /** + * Opens a socket pair of interconnected {@link DatagramChannel}s, using the given socket type. + * + * @param type The socket type. + * @return The new channel pair. + * @throws IOException on error. + */ + public static AFUNIXSocketPair openDatagram(AFSocketType type) + throws IOException { + return AFUNIXSelectorProvider.provider().openDatagramChannelPair(type); + } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFVSOCKSocketAddress.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFVSOCKSocketAddress.java index a9c8012e0..18a2fcefb 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFVSOCKSocketAddress.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFVSOCKSocketAddress.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,13 +33,15 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.newsclub.net.unix.pool.ObjectPool.Lease; + /** * An {@link AFSocketAddress} for VSOCK sockets. - * + * * @author Christian Kohlschütter */ public final class AFVSOCKSocketAddress extends AFSocketAddress { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; // do not change! private static final Pattern PAT_VSOCK_URI_HOST_AND_PORT = Pattern.compile( "^(?any|[0-9a-fx\\-]+)(\\.(?any|hypervisor|local|host|[0-9a-fx\\-]+))?(?:\\:(?[0-9]+))?$"); @@ -71,11 +73,17 @@ public final class AFVSOCKSocketAddress extends AFSocketAddress { */ public static final int VMADDR_PORT_ANY = -1; - private AFVSOCKSocketAddress(int port, final byte[] socketAddress, ByteBuffer nativeAddress) - throws SocketException { + private AFVSOCKSocketAddress(int port, final byte[] socketAddress, + Lease nativeAddress) throws SocketException { super(port, socketAddress, nativeAddress, addressFamily()); } + private static AFVSOCKSocketAddress newAFSocketAddress(int port, final byte[] socketAddress, + Lease nativeAddress) throws SocketException { + return newDeserializedAFSocketAddress(port, socketAddress, nativeAddress, addressFamily(), + AFVSOCKSocketAddress::new); + } + /** * Returns an {@link AFVSOCKSocketAddress} that refers to a given VSOCK port and CID; the "java * port" is set to -1. @@ -104,7 +112,7 @@ public static AFVSOCKSocketAddress ofHypervisorPort(int port) throws SocketExcep /** * Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to "any" * port on the hypervisor; the "java port" is set to -1. - * + * * @return A corresponding {@link AFVSOCKSocketAddress} instance. * @throws SocketException if the operation fails. */ @@ -115,7 +123,7 @@ public static AFVSOCKSocketAddress ofAnyHypervisorPort() throws SocketException /** * Returns an {@link AFVSOCKSocketAddress} that refers to the given port with the local/loopback * CID; the "java port" is set to -1. - * + * * @param port The VSOCK port. * @return A corresponding {@link AFVSOCKSocketAddress} instance. * @throws SocketException if the operation fails. @@ -127,7 +135,7 @@ public static AFVSOCKSocketAddress ofLocalPort(int port) throws SocketException /** * Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to "any" * port with the local/loopback CID; the "java port" is set to -1. - * + * * @return A corresponding {@link AFVSOCKSocketAddress} instance. * @throws SocketException if the operation fails. */ @@ -150,7 +158,7 @@ public static AFVSOCKSocketAddress ofHostPort(int port) throws SocketException { /** * Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to "any" * port on the host; the "java port" is set to -1. - * + * * @return A corresponding {@link AFVSOCKSocketAddress} instance. * @throws SocketException if the operation fails. */ @@ -161,7 +169,7 @@ public static AFVSOCKSocketAddress ofAnyHostPort() throws SocketException { /** * Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to "any" * port and CID; the "java port" is set to -1. - * + * * @return A corresponding {@link AFVSOCKSocketAddress} instance. * @throws SocketException if the operation fails. */ @@ -172,7 +180,7 @@ public static AFVSOCKSocketAddress ofAnyPort() throws SocketException { /** * Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to the * given port with "any CID"; the "java port" is set to -1. - * + * * @param port The VSOCK port. * @return A corresponding {@link AFVSOCKSocketAddress} instance. * @throws SocketException if the operation fails. @@ -198,7 +206,7 @@ public static AFVSOCKSocketAddress ofPortAndCID(int javaPort, int vsockPort, int /** * Returns an {@link AFVSOCKSocketAddress} given a special {@link InetAddress} that encodes the * byte sequence of an AF_VSOCK socket address, like those returned by {@link #wrapAddress()}. - * + * * @param address The "special" {@link InetAddress}. * @param port The port (use 0 for "none"). * @return The {@link AFVSOCKSocketAddress} instance. @@ -213,7 +221,7 @@ public static AFVSOCKSocketAddress unwrap(InetAddress address, int port) throws * Returns an {@link AFVSOCKSocketAddress} given a special {@link InetAddress} hostname that * encodes the byte sequence of an AF_VSOCK socket address, like those returned by * {@link #wrapAddress()}. - * + * * @param hostname The "special" hostname, as provided by {@link InetAddress#getHostName()}. * @param port The port (use 0 for "none"). * @return The {@link AFVSOCKSocketAddress} instance. @@ -226,7 +234,7 @@ public static AFVSOCKSocketAddress unwrap(String hostname, int port) throws Sock /** * Returns an {@link AFVSOCKSocketAddress} given a generic {@link SocketAddress}. - * + * * @param address The address to unwrap. * @return The {@link AFVSOCKSocketAddress} instance. * @throws SocketException if the operation fails, for example when an unsupported address is @@ -242,7 +250,7 @@ public static AFVSOCKSocketAddress unwrap(SocketAddress address) throws SocketEx /** * Returns the "VSOCK port" part of this address. - * + * * @return The VSOCK port identifier * @see #getPort() */ @@ -254,7 +262,7 @@ public int getVSOCKPort() { /** * Returns the "VSOCK CID" part of this address. - * + * * @return The VSOCK CID identifier. */ public int getVSOCKCID() { @@ -265,7 +273,7 @@ public int getVSOCKCID() { /** * Returns the "VSOCK reserved1" part of this address. - * + * * @return The "reserved1" identifier, which should be 0. */ public int getVSOCKReserved1() { @@ -313,7 +321,7 @@ public File getFile() throws FileNotFoundException { /** * Checks if an {@link InetAddress} can be unwrapped to an {@link AFVSOCKSocketAddress}. - * + * * @param addr The instance to check. * @return {@code true} if so. * @see #wrapAddress() @@ -325,7 +333,7 @@ public static boolean isSupportedAddress(InetAddress addr) { /** * Checks if a {@link SocketAddress} can be unwrapped to an {@link AFVSOCKSocketAddress}. - * + * * @param addr The instance to check. * @return {@code true} if so. * @see #unwrap(InetAddress, int) @@ -345,7 +353,7 @@ private static byte[] toBytes(int port, int cid) { /** * Returns the corresponding {@link AFAddressFamily}. - * + * * @return The address family instance. */ @SuppressWarnings("null") @@ -354,6 +362,10 @@ public static synchronized AFAddressFamily addressFamily() afVsock = AFAddressFamily.registerAddressFamily("vsock", // AFVSOCKSocketAddress.class, new AFSocketAddressConfig() { + private final AFSocketAddressConstructor addrConstr = + isUseDeserializationForInit() ? AFVSOCKSocketAddress::newAFSocketAddress + : AFVSOCKSocketAddress::new; + @Override protected AFVSOCKSocketAddress parseURI(URI u, int port) throws SocketException { return AFVSOCKSocketAddress.of(u, port); @@ -361,7 +373,7 @@ protected AFVSOCKSocketAddress parseURI(URI u, int port) throws SocketException @Override protected AFSocketAddressConstructor addressConstructor() { - return AFVSOCKSocketAddress::new; + return addrConstr; } @Override @@ -385,7 +397,7 @@ protected Set uriSchemes() { /** * Returns an {@link AFVSOCKSocketAddress} for the given URI, if possible. - * + * * @param uri The URI. * @return The address. * @throws SocketException if the operation fails. @@ -397,7 +409,7 @@ public static AFVSOCKSocketAddress of(URI uri) throws SocketException { /** * Returns an {@link AFVSOCKSocketAddress} for the given URI, if possible. - * + * * @param uri The URI. * @param overridePort The port to forcibly use, or {@code -1} for "don't override". * @return The address. @@ -508,7 +520,7 @@ public URI toURI(String scheme, URI template) throws IOException { portStr = "any"; break; default: - portStr = Integer.toUnsignedString(port); + portStr = toUnsignedString(port); break; } @@ -530,7 +542,7 @@ public URI toURI(String scheme, URI template) throws IOException { cidStr = "host"; break; default: - cidStr = Integer.toUnsignedString(cid); + cidStr = toUnsignedString(cid); break; } @@ -541,23 +553,23 @@ public URI toURI(String scheme, URI template) throws IOException { private static int parseInt(String v) { if (v.startsWith("0x")) { - return Integer.parseUnsignedInt(v.substring(2), 16); + return parseUnsignedInt(v.substring(2), 16); } else if (v.startsWith("-")) { return Integer.parseInt(v); } else { - return Integer.parseUnsignedInt(v); + return parseUnsignedInt(v, 10); } } /** * Checks if the given address could cover another address. - * + * * By default, this is only true if both addresses are regarded equal using * {@link #equals(Object)}. - * + * * However, implementations may support "wildcard" addresses, and this method would compare a * wildcard address against some non-wildcard address, for example. - * + * * @param covered The other address that could be covered by this address. * @return {@code true} if the other address could be covered. */ diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFVSOCKSocketImplExtensions.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFVSOCKSocketImplExtensions.java index fc7b65e66..a48f273b8 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFVSOCKSocketImplExtensions.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFVSOCKSocketImplExtensions.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ /** * VSOCK-specific code that resides in the native library. To be used by {@code AFVSOCKSocket} and * {@code AFVSOCKDatagramSocket} only. - * + * * @author Christian Kohlschütter */ public final class AFVSOCKSocketImplExtensions implements @@ -36,12 +36,12 @@ public final class AFVSOCKSocketImplExtensions implements /** * Returns the local CID. - * + * * If the system does not support vsock, or status about support cannot be retrieved, -1 * ({@link AFVSOCKSocketAddress#VMADDR_CID_ANY}) is returned. - * + * * The value may be cached upon initialization of the library. - * + * * @return The CID, or -1. * @throws IOException on error. */ diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AddressUnavailableSocketException.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AddressUnavailableSocketException.java index c892bcd13..5176f9530 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AddressUnavailableSocketException.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AddressUnavailableSocketException.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ /** * A {@link SocketException} that may be thrown upon some "address unavailable" condition from * native code (e.g., EADDRNOTAVAIL is returned). - * + * * @author Christian Kohlschütter */ public class AddressUnavailableSocketException extends InvalidSocketException { @@ -37,7 +37,7 @@ public AddressUnavailableSocketException() { /** * Constructs a new {@link AddressUnavailableSocketException}. - * + * * @param msg The error message. */ public AddressUnavailableSocketException(String msg) { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AncillaryDataSupport.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AncillaryDataSupport.java index e6c63764d..a1fd88c91 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AncillaryDataSupport.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AncillaryDataSupport.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,11 +20,11 @@ import java.io.Closeable; import java.io.FileDescriptor; import java.io.IOException; +import java.net.SocketException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -38,10 +38,10 @@ final class AncillaryDataSupport implements Closeable { .ancillaryBufMinLen() : 0; private final Map openReceivedFileDescriptors = Collections - .synchronizedMap(new HashMap()); + .synchronizedMap(new HashMap<>()); private final List receivedFileDescriptors = Collections.synchronizedList( - new LinkedList()); + new ArrayList<>()); // referenced from native code private ByteBuffer ancillaryReceiveBuffer = EMPTY_BUFFER; @@ -131,7 +131,12 @@ public void close() throws IOException { openReceivedFileDescriptors.remove(fdesc); } }; - NativeUnixSocket.attachCloseable(fdesc, cleanup); + + try { + NativeUnixSocket.attachCloseable(fdesc, cleanup); + } catch (SocketException e) { + // ignore (cannot attach) + } } this.receivedFileDescriptors.add(descriptors); @@ -193,10 +198,12 @@ void setOutboundFileDescriptors(FileDescriptor... fdescs) throws IOException { public void close() { synchronized (openReceivedFileDescriptors) { for (FileDescriptor desc : openReceivedFileDescriptors.keySet()) { - try { - NativeUnixSocket.close(desc); - } catch (Exception e) { - // ignore + if (desc.valid()) { + try { + NativeUnixSocket.close(desc); + } catch (Exception e) { + // ignore + } } } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/BrokenPipeSocketException.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/BrokenPipeSocketException.java index 55973cb62..a999fb1fd 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/BrokenPipeSocketException.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/BrokenPipeSocketException.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ /** * A {@link SocketException} indicating that a socket connection was broken ("broken pipe"). - * + * * @author Christian Kohlschütter */ public final class BrokenPipeSocketException extends SocketException { @@ -36,7 +36,7 @@ public BrokenPipeSocketException() { /** * Constructs a new {@link BrokenPipeSocketException}. - * + * * @param msg The error message. */ public BrokenPipeSocketException(String msg) { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/CleanableState.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/CleanableState.java index e77bfe952..8145779c3 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/CleanableState.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/CleanableState.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,10 +21,12 @@ import java.io.IOException; import java.lang.ref.Cleaner; +import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; + /** * This wrapper (along with the Java 8-specific counterpart in src/main/java8) allows us to * implement cleanup logic for objects that are garbage-collectable/no longer reachable. - * + * *

    * Usage: *

      @@ -46,9 +48,10 @@ *
    1. In Java 8 or earlier, {@link #finalize()} calls {@link #doClean()} directly.
    2. *
*

- * + * * @author Christian Kohlschütter */ +@IgnoreJRERequirement // see src/main/java8 abstract class CleanableState implements Closeable { private static final Cleaner CLEANER = Cleaner.create(); private final Cleaner.Cleanable cleanable; @@ -56,12 +59,13 @@ abstract class CleanableState implements Closeable { /** * Creates a state object to be used as an implementation detail of the specified observed * instance. - * + * * @param observed The observed instance (the outer class referencing this * {@link CleanableState}). */ protected CleanableState(Object observed) { - this.cleanable = CLEANER.register(observed, () -> doClean()); + this.cleanable = CLEANER.register(observed, () -> doClean()); // NOPMD.LambdaCanBeMethodReference + // (Retrolambda) } /** diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/CloseablePair.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/CloseablePair.java index 557c1eb97..8cb41f583 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/CloseablePair.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/CloseablePair.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ /** * A pair of two closeable items. - * + * * @param The type of the items. * @author Christian Kohlschütter */ @@ -36,7 +36,7 @@ public class CloseablePair implements Closeable { /** * Creates a pair of two items. - * + * * @param a The first item. * @param b The second item. */ @@ -46,7 +46,7 @@ public CloseablePair(T a, T b) { /** * Creates a pair of two items. - * + * * @param a The first item. * @param b The second item. * @param alsoClose Some closeable that is also closed upon {@link #close()}, or {@code null}. @@ -70,7 +70,7 @@ public final void close() throws IOException { /** * Returns the pair's first item. - * + * * @return The first item. */ public final @NonNull T getFirst() { @@ -79,7 +79,7 @@ public final void close() throws IOException { /** * Returns the pair's second item. - * + * * @return The second item. */ public final @NonNull T getSecond() { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/Closeables.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/Closeables.java index c2c3be5a9..cc16a353e 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/Closeables.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/Closeables.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,10 +26,11 @@ /** * A set of {@link Closeables} that can be closed at once. - * + * * @author Christian Kohlschütter */ public final class Closeables implements Closeable { + private boolean closed = false; private List> list; /** @@ -41,12 +42,13 @@ public Closeables() { /** * Creates a new {@link Closeables} instance, populating it with the given {@link Closeable} * objects. - * + * * @param closeable The {@link Closeable}s to add. */ public Closeables(Closeable... closeable) { + this.list = new ArrayList<>(); for (Closeable cl : closeable) { - this.list.add(new HardReference(cl)); + this.list.add(new HardReference<>(cl)); } } @@ -55,9 +57,18 @@ public void close() throws IOException { close(null); } + /** + * Checks if this instance has been closed already. + * + * @return {@code true} if closed. + */ + public synchronized boolean isClosed() { + return closed; + } + /** * Closes all registered closeables. - * + * * @param superException If set, any exceptions thrown in here will be chained to the given * exception via addSuppressed, and then thrown. * @throws IOException if an exception occurs. @@ -65,21 +76,32 @@ public void close() throws IOException { public void close(IOException superException) throws IOException { IOException exc = superException; - if (list != null) { - for (WeakReference ref : list) { - @SuppressWarnings("resource") - Closeable cl = ref.get(); - if (cl == null) { - continue; - } - try { - cl.close(); - } catch (IOException e) { - if (exc == null) { - exc = e; - } else { - exc.addSuppressed(e); - } + List> l; + synchronized (this) { + closed = true; + + l = this.list; + if (l == null) { + return; + } + + l = new ArrayList<>(l); + this.list = null; + } + + for (WeakReference ref : l) { + @SuppressWarnings("resource") + Closeable cl = ref.get(); + if (cl == null) { + continue; + } + try { + cl.close(); + } catch (IOException e) { + if (exc == null) { + exc = e; + } else { + exc.addSuppressed(e); } } } @@ -106,12 +128,15 @@ public V get() { /** * Adds the given closeable, but only using a weak reference. - * + * * @param closeable The closeable. - * @return {@code true} iff the closeable was added, {@code false} if it was {@code null} or - * already added before. + * @return {@code true} iff the closeable was added, {@code false} if it was {@code null}, already + * added before, or if the {@link Closeables} instance has been closed already. */ public synchronized boolean add(WeakReference closeable) { + if (closed) { + return false; + } Closeable cl = closeable.get(); if (cl == null) { // ignore @@ -133,7 +158,7 @@ public synchronized boolean add(WeakReference closeable) { /** * Adds the given closeable. - * + * * @param closeable The closeable. * @return {@code true} iff the closeable was added, {@code false} if it was {@code null} or * already added before. @@ -144,13 +169,13 @@ public synchronized boolean add(Closeable closeable) { /** * Removes the given closeable. - * + * * @param closeable The closeable. * @return {@code true} iff the closeable was removed, {@code fale} if it was {@code null} or not * previously added. */ public synchronized boolean remove(Closeable closeable) { - if (list == null || closeable == null) { + if (list == null || closeable == null || closed) { return false; } for (Iterator> it = list.iterator(); it.hasNext();) { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/ConnectionResetSocketException.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/ConnectionResetSocketException.java new file mode 100644 index 000000000..90daa8be4 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/ConnectionResetSocketException.java @@ -0,0 +1,45 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.net.SocketException; + +/** + * A {@link SocketException} indicating that a socket connection was reset. + * + * @author Christian Kohlschütter + */ +public final class ConnectionResetSocketException extends SocketException { + private static final long serialVersionUID = 1L; + + /** + * Constructs a new {@link ConnectionResetSocketException}. + */ + public ConnectionResetSocketException() { + super(); + } + + /** + * Constructs a new {@link ConnectionResetSocketException}. + * + * @param msg The error message. + */ + public ConnectionResetSocketException(String msg) { + super(msg); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/DatagramSocketImplShim.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/DatagramSocketImplShim.java index 6ee295499..1c715b8b3 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/DatagramSocketImplShim.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/DatagramSocketImplShim.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ /** * A shim that is filled with Java version-specific overrides. This variant is for Java 9 and above. - * + * * @author Christian Kohlschütter */ abstract class DatagramSocketImplShim extends DatagramSocketImpl { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/DatagramSocketShim.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/DatagramSocketShim.java new file mode 100644 index 000000000..98dfada09 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/DatagramSocketShim.java @@ -0,0 +1,73 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.DatagramSocketImpl; +import java.net.SocketOption; + +import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; + +@IgnoreJRERequirement // see src/main/java8 +abstract class DatagramSocketShim extends DatagramSocket { + + protected DatagramSocketShim(DatagramSocketImpl impl) { + super(impl); + } + + @Override + public T getOption(SocketOption name) throws IOException { + if (name instanceof AFSocketOption) { + return getOption((AFSocketOption) name); + } else { + return super.getOption(name); + } + } + + @Override + public DatagramSocket setOption(SocketOption name, T value) throws IOException { + if (name instanceof AFSocketOption) { + return setOption((AFSocketOption) name, value); + } else { + return super.setOption(name, value); + } + } + + /** + * Returns the value of a junixsocket socket option. + * + * @param The type of the socket option value. + * @param name The socket option. + * @return The value of the socket option. + * @throws IOException on error. + */ + public abstract T getOption(AFSocketOption name) throws IOException; + + /** + * Sets the value of a socket option. + * + * @param The type of the socket option value. + * @param name The socket option. + * @param value The value of the socket option. + * @return this DatagramSocket. + * @throws IOException on error. + */ + @SuppressWarnings("PMD.LinguisticNaming") + public abstract DatagramSocket setOption(AFSocketOption name, T value) throws IOException; +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/FileDescriptorAccess.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/FileDescriptorAccess.java index 15f5da800..8c75a4006 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/FileDescriptorAccess.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/FileDescriptorAccess.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,13 +22,13 @@ /** * Something that has a {@link FileDescriptor}. - * + * * @author Christian Kohlschütter */ public interface FileDescriptorAccess { /** * Returns the corresponding {@link FileDescriptor}. - * + * * @return The corresponding {@link FileDescriptor}. * @throws IOException on error. */ diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/FileDescriptorCast.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/FileDescriptorCast.java index e77caa507..0fd1613d2 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/FileDescriptorCast.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/FileDescriptorCast.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,22 +17,28 @@ */ package org.newsclub.net.unix; +import java.io.Closeable; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.ProcessBuilder.Redirect; +import java.net.DatagramSocket; +import java.net.ServerSocket; import java.net.Socket; +import java.nio.channels.DatagramChannel; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; import java.nio.channels.WritableByteChannel; +import java.nio.channels.spi.AbstractSelectableChannel; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.function.Function; import org.eclipse.jdt.annotation.NonNull; @@ -46,39 +52,59 @@ *

*

  * FileDescriptor fd;
- * 
+ *
  * // succeeds if fd refers to an AF_UNIX stream socket
- * AFUNIXSocket socket = FileDescriptorCast.using(fd).as(AFUNIXSocket.class); 
- * 
+ * AFUNIXSocket socket = FileDescriptorCast.using(fd).as(AFUNIXSocket.class);
+ *
  * // succeeds if fd refers to an AF_UNIX datagram socket
- * AFUNIXDatagramChannel channel = FileDescriptorCast.using(fd).as(AFUNIXDatagramChannel.class); 
- * 
+ * AFUNIXDatagramChannel channel = FileDescriptorCast.using(fd).as(AFUNIXDatagramChannel.class);
+ *
  * // always succeeds
- * InputStream in = FileDescriptorCast.using(fd).as(InputStream.class); 
- * OutputStream in = FileDescriptorCast.using(fd).as(OutputStream.class); 
+ * InputStream in = FileDescriptorCast.using(fd).as(InputStream.class);
+ * OutputStream in = FileDescriptorCast.using(fd).as(OutputStream.class);
  * 
*

- * IMPORTANT: On some platforms (e.g., Solaris, Illumos) you may need to re-apply a read timeout - * (e.g., using {@link Socket#setSoTimeout(int)}) after obtaining the socket. - *

+ * Important notes + *
    + *
  1. On some platforms (e.g., Solaris, Illumos) you may need to re-apply a read timeout (e.g., + * using {@link Socket#setSoTimeout(int)}) after obtaining the socket.
  2. + *
  3. You may lose Java port information for {@link AFSocketAddress} implementations that do not + * encode this information directly (such as {@link AFUNIXSocketAddress} and + * {@link AFTIPCSocketAddress}).
  4. + *
  5. The "blocking" state of a socket may be forcibly changed to "blocking" when performing the + * cast, especially when casting to {@link Socket}, {@link DatagramSocket} or {@link ServerSocket} + * and any of their subclasses where "blocking" is the expected state.
  6. + *
  7. When calling {@link #using(FileDescriptor)} for a {@link FileDescriptor} obtained from + * another socket or other resource in the same JVM (i.e., not from another process), especially for + * sockets provided by junixsocket itself, there is a chance that the garbage collector may clean up + * the original socket at an opportune moment, thereby closing the resource underlying the shared + * {@link FileDescriptor} prematurely. *

    - * Note that you may also lose Java port information for {@link AFSocketAddress} implementations - * that do not encode this information directly (such as {@link AFUNIXSocketAddress} and - * {@link AFTIPCSocketAddress}). - *

    - * + * This is considered an edge-case, and deliberately not handled automatically for performance and + * portability reasons: We would have to do additional reference counting on all FileDescriptor + * instances, either through patching {@code FileCleanable} or a shared data structure. + *

    + * The issue can be prevented by keeping a reference to the original object, such as keeping it in + * an enclosing try-with-resources block or as a member variable, for example. Alternatively, using + * a "duplicate" file descriptor (via {@link #duplicating(FileDescriptor)}) circumvents this + * problem, at the cost of using additional system resources.

  8. + *
  9. As a consequence of the previous point: For {@link #using(FileDescriptor)}: when casting file + * descriptors that belong to a junixsocket-controlled sockets, the target socket is configured in a + * way such that garbage collection will not automatically close the target's underlying file + * descriptor (but still potentially any file descriptors received from other processes via + * ancillary messages).
  10. + *
  11. The same restrictions as for {@link #using(FileDescriptor)} apply to + * {@link #unsafeUsing(int)} as well.
  12. + *
+ * * @author Christian Kohlschütter */ +@SuppressWarnings("PMD.CouplingBetweenObjects") public final class FileDescriptorCast implements FileDescriptorAccess { private static final Map, CastingProviderMap> PRIMARY_TYPE_PROVIDERS_MAP = Collections .synchronizedMap(new HashMap<>()); - private final FileDescriptor fdObj; - - private int localPort = 0; - private int remotePort = 0; - - private static final Function FD_IS_PROVIDER = System + private static final AFFunction FD_IS_PROVIDER = System .getProperty("osv.version") != null ? LenientFileInputStream::new : FileInputStream::new; private static final CastingProviderMap GLOBAL_PROVIDERS_FINAL = new CastingProviderMap() { @@ -108,7 +134,6 @@ public WritableByteChannel provideAs(FileDescriptorCast fdc, } }); addProvider(ReadableByteChannel.class, new CastingProvider() { - @SuppressWarnings("resource") @Override public ReadableByteChannel provideAs(FileDescriptorCast fdc, Class desiredType) throws IOException { @@ -138,6 +163,25 @@ public FileInputStream provideAs(FileDescriptorCast fdc, return FD_IS_PROVIDER.apply(fdc.getFileDescriptor()); } }); + addProvider(FileDescriptor.class, new CastingProvider() { + @Override + public FileDescriptor provideAs(FileDescriptorCast fdc, + Class desiredType) throws IOException { + return fdc.getFileDescriptor(); + } + }); + addProvider(Integer.class, new CastingProvider() { + @Override + public Integer provideAs(FileDescriptorCast fdc, Class desiredType) + throws IOException { + FileDescriptor fd = fdc.getFileDescriptor(); + int val = fd.valid() ? NativeUnixSocket.getFD(fd) : -1; + if (val == -1) { + throw new IOException("Not a valid file descriptor"); + } + return val; + } + }); if (AFSocket.supports(AFSocketCapability.CAPABILITY_FD_AS_REDIRECT)) { addProvider(Redirect.class, new CastingProvider() { @@ -156,6 +200,19 @@ public Redirect provideAs(FileDescriptorCast fdc, Class desire } }; + private static final int FD_IN = getFdIfPossible(FileDescriptor.in); + private static final int FD_OUT = getFdIfPossible(FileDescriptor.out); + private static final int FD_ERR = getFdIfPossible(FileDescriptor.err); + + static { + registerGenericSocketSupport(); + } + + private final FileDescriptor fdObj; + + private int localPort = 0; + private int remotePort = 0; + private final CastingProviderMap cpm; private FileDescriptorCast(FileDescriptor fdObj, CastingProviderMap cpm) { @@ -163,6 +220,20 @@ private FileDescriptorCast(FileDescriptor fdObj, CastingProviderMap cpm) { this.cpm = Objects.requireNonNull(cpm); } + private static int getFdIfPossible(FileDescriptor fd) { + if (!NativeUnixSocket.isLoaded()) { + return -1; + } + try { + if (!fd.valid()) { + return -1; + } + return NativeUnixSocket.getFD(fd); + } catch (IOException e) { + return -1; + } + } + private static void registerCastingProviders(Class primaryType, CastingProviderMap cpm) { Objects.requireNonNull(primaryType); CastingProviderMap prev; @@ -184,20 +255,24 @@ static
void registerCastingProviders( protected void addProviders() { addProviders(GLOBAL_PROVIDERS); - final CastingProvider> cpSocket = (fdc, desiredType) -> AFSocket.newInstance( - config.socketConstructor(), (AFSocketFactory) null, fdc.getFileDescriptor(), - fdc.localPort, fdc.remotePort); - final CastingProvider> cpServerSocket = (fdc, - desiredType) -> AFServerSocket.newInstance(config.serverSocketConstructor(), fdc - .getFileDescriptor(), fdc.localPort, fdc.remotePort); - - addProvider(socketClass, cpSocket); - addProvider(config.serverSocketClass(), cpServerSocket); - - addProvider(config.socketChannelClass(), (fdc, desiredType) -> cpSocket.provideAs(fdc, - AFSocket.class).getChannel()); - addProvider(config.serverSocketChannelClass(), (fdc, desiredType) -> cpServerSocket - .provideAs(fdc, AFServerSocket.class).getChannel()); + final CastingProviderSocketOrChannel> cpSocketOrChannel = (fdc, desiredType, + isChannel) -> reconfigure(isChannel, AFSocket.newInstance(config.socketConstructor(), + (AFSocketFactory) null, fdc.getFileDescriptor(), fdc.localPort, fdc.remotePort)); + final CastingProviderSocketOrChannel> cpServerSocketOrChannel = (fdc, + desiredType, isChannel) -> reconfigure(isChannel, AFServerSocket.newInstance(config + .serverSocketConstructor(), fdc.getFileDescriptor(), fdc.localPort, + fdc.remotePort)); + + registerGenericSocketProviders(); + + addProvider(socketClass, (fdc, desiredType) -> cpSocketOrChannel.provideAs(fdc, desiredType, + false)); + addProvider(config.serverSocketClass(), (fdc, desiredType) -> cpServerSocketOrChannel + .provideAs(fdc, desiredType, false)); + addProvider(config.socketChannelClass(), (fdc, desiredType) -> cpSocketOrChannel.provideAs( + fdc, AFSocket.class, true).getChannel()); + addProvider(config.serverSocketChannelClass(), (fdc, desiredType) -> cpServerSocketOrChannel + .provideAs(fdc, AFServerSocket.class, true).getChannel()); } }); @@ -208,14 +283,17 @@ protected void addProviders() { protected void addProviders() { addProviders(GLOBAL_PROVIDERS); - final CastingProvider> cpDatagramSocket = (fdc, - desiredType) -> AFDatagramSocket.newInstance(config.datagramSocketConstructor(), fdc - .getFileDescriptor(), fdc.localPort, fdc.remotePort); + final CastingProviderSocketOrChannel> cpDatagramSocketOrChannel = (fdc, + desiredType, isChannel) -> reconfigure(isChannel, AFDatagramSocket.newInstance(config + .datagramSocketConstructor(), fdc.getFileDescriptor(), fdc.localPort, + fdc.remotePort)); - addProvider(datagramSocketClass, cpDatagramSocket); + registerGenericDatagramSocketProviders(); - addProvider(config.datagramChannelClass(), (fdc, desiredType) -> cpDatagramSocket.provideAs( - fdc, AFDatagramSocket.class).getChannel()); + addProvider(datagramSocketClass, (fdc, desiredType) -> cpDatagramSocketOrChannel.provideAs( + fdc, desiredType, false)); + addProvider(config.datagramChannelClass(), (fdc, desiredType) -> cpDatagramSocketOrChannel + .provideAs(fdc, AFDatagramSocket.class, true).getChannel()); } }); } @@ -224,12 +302,47 @@ private abstract static class CastingProviderMap { private final Map, CastingProvider> providers = new HashMap<>(); private final Set> classes = Collections.unmodifiableSet(providers.keySet()); + @SuppressWarnings("PMD.ConstructorCallsOverridableMethod") protected CastingProviderMap() { addProviders(); addProviders(GLOBAL_PROVIDERS_FINAL); } + @SuppressWarnings("null") + protected void registerGenericSocketProviders() { + final CastingProviderSocketOrChannel> cpSocketOrChannelGeneric = + (fdc, desiredType, isChannel) -> reconfigure(isChannel, AFSocket.newInstance( + AFGenericSocket::new, (AFSocketFactory) null, fdc + .getFileDescriptor(), fdc.localPort, fdc.remotePort)); + final CastingProviderSocketOrChannel> cpServerSocketOrChannelGeneric = + (fdc, desiredType, isChannel) -> reconfigure(isChannel, AFServerSocket.newInstance( + AFGenericServerSocket::new, fdc.getFileDescriptor(), fdc.localPort, fdc.remotePort)); + + addProvider(AFGenericSocket.class, (fdc, desiredType) -> cpSocketOrChannelGeneric.provideAs( + fdc, desiredType, false)); + addProvider(AFGenericServerSocket.class, (fdc, desiredType) -> cpServerSocketOrChannelGeneric + .provideAs(fdc, desiredType, false)); + addProvider(AFGenericSocketChannel.class, (fdc, desiredType) -> cpSocketOrChannelGeneric + .provideAs(fdc, AFSocket.class, true).getChannel()); + addProvider(AFGenericServerSocketChannel.class, (fdc, + desiredType) -> cpServerSocketOrChannelGeneric.provideAs(fdc, AFServerSocket.class, true) + .getChannel()); + } + + @SuppressWarnings("null") + protected void registerGenericDatagramSocketProviders() { + final CastingProviderSocketOrChannel> cpDatagramSocketOrChannelGeneric = + (fdc, desiredType, isChannel) -> reconfigure(isChannel, AFDatagramSocket.newInstance( + AFGenericDatagramSocket::new, fdc.getFileDescriptor(), fdc.localPort, + fdc.remotePort)); + + addProvider(AFDatagramSocket.class, (fdc, desiredType) -> cpDatagramSocketOrChannelGeneric + .provideAs(fdc, desiredType, false)); + addProvider(AFDatagramChannel.class, (fdc, desiredType) -> cpDatagramSocketOrChannelGeneric + .provideAs(fdc, AFDatagramSocket.class, true).getChannel()); + } + protected abstract void addProviders(); protected final void addProvider(Class type, CastingProvider cp) { @@ -268,9 +381,20 @@ private interface CastingProvider { T provideAs(FileDescriptorCast fdc, Class desiredType) throws IOException; } + @FunctionalInterface + private interface CastingProviderSocketOrChannel { + T provideAs(FileDescriptorCast fdc, Class desiredType, boolean isChannel) + throws IOException; + } + /** * Creates a {@link FileDescriptorCast} using the given file descriptor. - * + *

+ * Note that if any resource that also references this {@link FileDescriptor} is + * garbage-collected, the cleanup for that object may close the referenced {@link FileDescriptor}, + * thereby resulting in premature connection losses, etc. See {@link #duplicating(FileDescriptor)} + * for a solution to this problem. + * * @param fdObj The file descriptor. * @return The {@link FileDescriptorCast} instance. * @throws IOException on error, especially if the given file descriptor is invalid or @@ -280,6 +404,7 @@ public static FileDescriptorCast using(FileDescriptor fdObj) throws IOException if (!fdObj.valid()) { throw new IOException("Not a valid file descriptor"); } + Class primaryType = NativeUnixSocket.isLoaded() ? NativeUnixSocket.primaryType(fdObj) : null; if (primaryType == null) { primaryType = FileDescriptor.class; @@ -291,17 +416,100 @@ public static FileDescriptorCast using(FileDescriptor fdObj) throws IOException return new FileDescriptorCast(fdObj, map == null ? GLOBAL_PROVIDERS : map); } + /** + * Creates a {@link FileDescriptorCast} using a duplicate of the given file descriptor. + *

+ * Duplicating a file descriptor is performed at the system-level, which means an additional file + * descriptor pointing to the same resource as the original is created by the operating system. + *

+ * The advantage of using {@link #duplicating(FileDescriptor)} over {@link #using(FileDescriptor)} + * is that neither implicit garbage collection nor an explicit call to {@link Closeable#close()} + * on a resource owning the original {@link FileDescriptor} affects the availability of the + * resource from the target of the cast. + * + * @param fdObj The file descriptor to duplicate. + * @return The {@link FileDescriptorCast} instance. + * @throws IOException on error, especially if the given file descriptor is invalid or + * unsupported, or if duplicating fails or is unsupported. + */ + public static FileDescriptorCast duplicating(FileDescriptor fdObj) throws IOException { + if (!fdObj.valid()) { + throw new IOException("Not a valid file descriptor"); + } + + FileDescriptor duplicate = NativeUnixSocket.duplicate(fdObj, new FileDescriptor()); + if (duplicate == null) { + throw new IOException("Could not duplicate file descriptor"); + } + return using(duplicate); + } + + /** + * Creates a {@link FileDescriptorCast} using the given native file descriptor value. + *

+ * This method is inherently unsafe as it may + *

    + *
  1. make assumptions on the internal system representation of a file descriptor (which differs + * between Windows and Unix, for example).
  2. + *
  3. provide access to resources that are otherwise not accessible
  4. + *
+ *

+ * Note that attempts are made to reuse {@link FileDescriptor#in}, {@link FileDescriptor#out}, and + * {@link FileDescriptor#err}, respectively. + * + * @param fd The system-native file descriptor value. + * @return The {@link FileDescriptorCast} instance. + * @throws IOException on error, especially if the given file descriptor is invalid or + * unsupported, or when "unsafe" operations are unavailable or manually disabled for the + * current environment. + */ + @Unsafe + public static FileDescriptorCast unsafeUsing(int fd) throws IOException { + AFSocket.ensureUnsafeSupported(); + + FileDescriptor fdObj; + if (fd == -1) { + throw new IOException("Not a valid file descriptor"); + } else if (fd == FD_IN) { + fdObj = FileDescriptor.in; + } else if (fd == FD_OUT) { + fdObj = FileDescriptor.out; + } else if (fd == FD_ERR) { + fdObj = FileDescriptor.err; + } else { + fdObj = null; + } + + if (fdObj != null) { + int check = getFdIfPossible(fdObj); + if (fd == check) { + return using(fdObj); + } + } + + fdObj = new FileDescriptor(); + NativeUnixSocket.initFD(fdObj, fd); + + return using(fdObj); + } + private static void triggerInit() { - AFUNIXSocketAddress.addressFamily().getClass(); // trigger registration - AFTIPCSocketAddress.addressFamily().getClass(); // trigger registration + for (AFAddressFamily family : new AFAddressFamily[] { + AFUNIXSocketAddress.addressFamily(), // + AFTIPCSocketAddress.addressFamily(), // + AFVSOCKSocketAddress.addressFamily(), // + AFSYSTEMSocketAddress.addressFamily(), // + }) { + Objects.requireNonNull(family.getClass()); // trigger init + } } /** * Registers the given port number as the "local port" for this file descriptor. - * + * * Important: This only changes the state of this instance. The actual file descriptor is not * affected. - * + * * @param port The port to assign to (must be >= 0). * @return This instance. */ @@ -315,10 +523,10 @@ public FileDescriptorCast withLocalPort(int port) { /** * Registers the given port number as the "remote port" for this file descriptor. - * + * * Important: This only changes the state of this instance. The actual file descriptor is not * affected. - * + * * @param port The port to assign to (must be >= 0). * @return This instance. */ @@ -332,7 +540,7 @@ public FileDescriptorCast withRemotePort(int port) { /** * Casts this instance to the desired type. - * + * * @param The desired type. * @param desiredType The class of the desired type. * @return s An instance of the desired type. @@ -357,7 +565,7 @@ public FileDescriptorCast withRemotePort(int port) { /** * Checks if the instance can be cast as the given desired type (using {@link #as(Class)}). - * + * * @param desiredType The class of the desired type. * @return {@code true} if the cast can be made. * @throws IOException on error. @@ -370,7 +578,7 @@ public boolean isAvailable(Class desiredType) throws IOException { /** * Returns a collection of available types this instance can be cast to (using * {@link #as(Class)}). - * + * * @return The collection of available types. */ public Set> availableTypes() { @@ -402,4 +610,130 @@ public int available() throws IOException { } } } + + /** + * Add support for otherwise unsupported sockets. + */ + private static void registerGenericSocketSupport() { + registerCastingProviders(Socket.class, new CastingProviderMap() { + + @Override + protected void addProviders() { + addProviders(GLOBAL_PROVIDERS); + + registerGenericSocketProviders(); + } + }); + + registerCastingProviders(DatagramSocket.class, new CastingProviderMap() { + @Override + protected void addProviders() { + addProviders(GLOBAL_PROVIDERS); + + registerGenericDatagramSocketProviders(); + } + }); + } + + @SuppressWarnings("null") + private static > S reconfigure(boolean isChannel, S socket) + throws IOException { + reconfigure(isChannel, socket.getChannel()); + socket.getAFImpl().getCore().disableCleanFd(); + return socket; + } + + @SuppressWarnings("null") + private static > S reconfigure(boolean isChannel, S socket) + throws IOException { + reconfigure(isChannel, socket.getChannel()); + socket.getAFImpl().getCore().disableCleanFd(); + return socket; + } + + @SuppressWarnings("null") + private static > S reconfigure(boolean isChannel, S socket) + throws IOException { + reconfigure(isChannel, socket.getChannel()); + socket.getAFImpl().getCore().disableCleanFd(); + return socket; + } + + /** + * Reconfigures the Java-side of the socket/socket channel such that its state is compatible with + * the native socket's state. This is necessary to properly configure blocking/non-blocking state, + * as that is cached on the Java side. + *

+ * If {@code isChannel} is false, then we want to cast to a {@link Socket}, {@link DatagramSocket} + * or {@link ServerSocket}, which means blocking I/O is desired. If the underlying native socket + * is configured non-blocking, we need to reset the state to "blocking" accordingly. + *

+ * If {@code isChannel} is true, then we want to cast to a {@link SocketChannel}, + * {@link DatagramChannel} or {@link ServerSocketChannel}, in which case the blocking state should + * be preserved, if possible. It is then up to the user to check blocking state via + * {@link AbstractSelectableChannel#isBlocking()} prior to using the socket. + *

+ * Note that on Windows, it may be impossible to query the blocking state from an external socket, + * so the state is always forcibly set to "blocking". + * + * @param The type. + * @param isChannel The desired cast type (socket=set to blocking, or channel=preserve state). + * @param socketChannel The channel. + * @throws IOException on error. + */ + private static <@NonNull S extends AFSomeSocketChannel> void reconfigure(boolean isChannel, + S socketChannel) throws IOException { + if (isChannel) { + reconfigureKeepBlockingState(socketChannel); + } else { + reconfigureSetBlocking(socketChannel); + } + } + + private static <@NonNull S extends AFSomeSocketChannel> void reconfigureKeepBlockingState( + S socketChannel) throws IOException { + int result = NativeUnixSocket.checkBlocking(socketChannel.getFileDescriptor()); + + boolean blocking; + switch (result) { + case 0: + blocking = false; + break; + case 1: + blocking = true; + break; + case 2: + // need to reconfigure/forcibly override any cached result -> set to blocking by default + socketChannel.configureBlocking(false); + socketChannel.configureBlocking(true); + return; + default: + throw new OperationNotSupportedSocketException("Invalid blocking state"); + } + + socketChannel.configureBlocking(blocking); + } + + private static <@NonNull S extends AFSomeSocketChannel> void reconfigureSetBlocking( + S socketChannel) throws IOException { + int result = NativeUnixSocket.checkBlocking(socketChannel.getFileDescriptor()); + + switch (result) { + case 0: + // see below + break; + case 1: + // already blocking, nothing to do + return; + case 2: + // need to reconfigure/forcibly override any cached result -> set to blocking by default + // see below + break; + default: + throw new OperationNotSupportedSocketException("Invalid blocking state"); + } + + socketChannel.configureBlocking(false); + socketChannel.configureBlocking(true); + } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/HostAndPort.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/HostAndPort.java index ba1782759..c3b0d7c76 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/HostAndPort.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/HostAndPort.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ /** * Hostname and port. - * + * * @author Christian Kohlschütter */ public final class HostAndPort { @@ -39,7 +39,7 @@ public final class HostAndPort { /** * Creates a new hostname and port combination. - * + * * @param hostname The hostname. * @param port The port, or {@code -1} for "no port". */ @@ -88,7 +88,7 @@ public String toString() { /** * Tries to extract hostname and port information from the given URI. - * + * * @param u The URI to extract from. * @return The parsed {@link HostAndPort} instance. * @throws SocketException on error. @@ -130,7 +130,7 @@ private static String urlEncode(String s) { /** * Returns the hostname. - * + * * @return The hostname. */ public String getHostname() { @@ -139,7 +139,7 @@ public String getHostname() { /** * Returns the port, or {@code -1} for "no port specified". - * + * * @return The port. */ public int getPort() { @@ -148,7 +148,7 @@ public int getPort() { /** * Returns a URI with this hostname and port. - * + * * @param scheme The scheme to use. * @return The URI. */ @@ -159,7 +159,7 @@ public URI toURI(String scheme) { /** * Returns a URI with this hostname and port, potentially reusing other URI parameters from the * given template URI (authority, path, query, fragment). - * + * * @param scheme The scheme to use. * @param template The template. or {@code null}. * @return The URI. @@ -192,7 +192,7 @@ public URI toURI(String scheme, URI template) { /** * Returns a URI with this hostname and port, potentially using other URI parameters from the * given set of parameters. - * + * * @param scheme The scheme to use. * @param rawAuthority The raw authority field, or {@code null}. * @param rawPath The raw path field, or {@code null}. diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/InterruptibleChannelUtil.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/InterruptibleChannelUtil.java new file mode 100644 index 000000000..aea10314f --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/InterruptibleChannelUtil.java @@ -0,0 +1,149 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.IOException; +import java.nio.channels.AsynchronousCloseException; +import java.nio.channels.ClosedByInterruptException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.NotYetBoundException; +import java.nio.channels.NotYetConnectedException; +import java.nio.channels.spi.AbstractInterruptibleChannel; +import java.util.Objects; + +/** + * Helper methods when working with {@link AbstractInterruptibleChannel} subclasses. + * + * @author Christian Kohlschütter + */ +final class InterruptibleChannelUtil { + /** + * Reference to the protected {@code AbstractInterruptibleChannel#end(boolean)} method. + */ + @FunctionalInterface + interface EndMethod { + void end(boolean completed) throws AsynchronousCloseException; + } + + /** + * Wrapper method that calls {@code AbstractInterruptibleChannel#end(boolean)}, making sure the + * socket is closed and the {@link Thread#interrupted()} state is set correctly upon error. + * + * @param channel The channel. + * @param end The reference to the protected {@code AbstractInterruptibleChannel#end(boolean)} + * method. + * @param complete {@code true} if the block started with {@code begin} succeeded without an + * exception. + * @param exception An optional exception that was caught in the try-catch-finally block. + * @throws AsynchronousCloseException on error. + */ + static void endInterruptable(AFSomeSocketChannel channel, EndMethod end, boolean complete, + Exception exception) throws AsynchronousCloseException { + if (!complete) { + if (exception instanceof ClosedChannelException) { + // we already have caught a valid exception; we don't need to throw one from within "end" + complete = true; + } + } + try { + end.end(complete); + } catch (AsynchronousCloseException e) { + throw closeAndThrow(channel, e); + } + } + + private static T closeAndThrow(AFSomeSocketChannel channel, T exc) { + Objects.requireNonNull(exc); + if (channel.isOpen()) { + try { + channel.close(); + } catch (IOException e2) { + exc.addSuppressed(e2); + } + } + return exc; + } + + static IOException ioExceptionOrThrowRuntimeException(Exception exception) { + if (exception instanceof IOException) { + return (IOException) exception; + } else if (exception instanceof RuntimeException) { + throw (RuntimeException) exception; + } else { + throw new IllegalStateException(exception); + } + } + + /** + * Makes sure that upon an exception that is documented to have the channel be closed the channel + * is indeed closed before throwing that exception. If the exception is also documented to have + * the "Thread interrupted" state be set, make sure that this state is actually set as well. + * + * @param channel The channel to work with. + * @param e The exception + * @return The exception. + */ + @SuppressWarnings("PMD.CognitiveComplexity") + static Exception handleException(AFSomeSocketChannel channel, IOException e) { + if (e instanceof NotConnectedSocketException) { + return (NotYetConnectedException) new NotYetConnectedException().initCause(e); + } else if (e instanceof NotBoundSocketException) { + return (NotYetBoundException) new NotYetBoundException().initCause(e); + } + + if (e instanceof InvalidArgumentSocketException) { + if (channel instanceof AFServerSocketChannel) { + AFServerSocketChannel sc = (AFServerSocketChannel) channel; + if (!sc.socket().isBound()) { + return (NotYetBoundException) new NotYetBoundException().initCause(e); + } + } else if (channel instanceof AFSocketChannel) { + if (!((AFSocketChannel) channel).socket().isConnected()) { + return (NotYetConnectedException) new NotYetConnectedException().initCause(e); + } + } + } + + if (e instanceof SocketClosedException || e instanceof ClosedChannelException + || e instanceof BrokenPipeSocketException) { + Thread t = Thread.currentThread(); + + if (e instanceof SocketClosedByInterruptException + || e instanceof ClosedByInterruptException) { + if (!t.isInterrupted()) { + t.interrupt(); + } + } + + if (!(e instanceof ClosedChannelException)) { + // Make sure the caught exception is transformed into the expected exception + if (t.isInterrupted()) { + e = (ClosedByInterruptException) new ClosedByInterruptException().initCause(e); + } else if (e instanceof BrokenPipeSocketException) { + e = (AsynchronousCloseException) new AsynchronousCloseException().initCause(e); + } else { + e = (ClosedChannelException) new ClosedChannelException().initCause(e); + } + } + + return closeAndThrow(channel, e); + } else { + return e; + } + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/InvalidArgumentSocketException.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/InvalidArgumentSocketException.java index b644cfc37..1e792ee7f 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/InvalidArgumentSocketException.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/InvalidArgumentSocketException.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ /** * A {@link SocketException} that may be thrown upon some "invalid argument" being passed into * native code (i.e., EINVAL is returned). - * + * * @author Christian Kohlschütter */ public class InvalidArgumentSocketException extends InvalidSocketException { @@ -37,7 +37,7 @@ public InvalidArgumentSocketException() { /** * Constructs a new {@link InvalidArgumentSocketException}. - * + * * @param msg The error message. */ public InvalidArgumentSocketException(String msg) { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/InvalidSocketException.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/InvalidSocketException.java index c1aaa8ab8..4670e19a9 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/InvalidSocketException.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/InvalidSocketException.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ /** * A {@link SocketException} that may be thrown upon some "invalid" state, mostly detected in native * code. - * + * * @author Christian Kohlschütter * @see InvalidArgumentSocketException * @see AddressUnavailableSocketException @@ -40,7 +40,7 @@ public InvalidSocketException() { /** * Constructs a new {@link InvalidSocketException}. - * + * * @param msg The error message. */ public InvalidSocketException(String msg) { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/Java7Util.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/Java7Util.java new file mode 100644 index 000000000..4a231d5fa --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/Java7Util.java @@ -0,0 +1,42 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; +import org.eclipse.jdt.annotation.Nullable; + +/** + * Helper class to allow codebase to remain compatible with Java 7 (via retrolambda, + * animal-sniffer). + * + * @author Christian Kohlschütter + */ +@IgnoreJRERequirement // see src/main/java8 +final class Java7Util { + static <@Nullable U> AFFuture supplyAsync(AFSupplier supplier) { + return CompletableFuture.supplyAsync(supplier::get)::get; + } + + static V computeIfAbsent(Map map, K key, + AFFunction mappingFunction) { + return map.computeIfAbsent(key, mappingFunction::apply); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/KnownJavaBugIOException.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/KnownJavaBugIOException.java new file mode 100644 index 000000000..041bf17af --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/KnownJavaBugIOException.java @@ -0,0 +1,65 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.IOException; + +/** + * Thrown when a known Java/JRE/JDK bug was encountered. + * + * @author Christian Kohlschütter + */ +public class KnownJavaBugIOException extends IOException { + private static final long serialVersionUID = 1L; + + /** + * Constructs an {@code KnownJDKBugIOException} with {@code null} as its error detail message. + */ + public KnownJavaBugIOException() { + super(); + } + + /** + * Constructs an {@code IOException} with the specified detail message and cause. + * + * @param message The message. + * @param cause The cause. + */ + public KnownJavaBugIOException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs an {@code IOException} with the specified detail message. + * + * @param message The message. + */ + public KnownJavaBugIOException(String message) { + super(message); + } + + /** + * Constructs an {@code IOException} with the specified cause and {@code null} as its error detail + * message. + * + * @param cause The cause. + */ + public KnownJavaBugIOException(Throwable cause) { + super(cause); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/MapValueSet.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/MapValueSet.java new file mode 100644 index 000000000..cfd75a351 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/MapValueSet.java @@ -0,0 +1,328 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.util.Collection; +import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * A {@link Set} that is a view on the keys of a {@link Map} that have a certain value. + *

+ * The value is controlled by the concrete subclass ({@link #getValue()}). It can, for example, be a + * boolean or a counter, depending on the use case. If the value is equal to a "removed" sentinel + * value. + * + * @param The element type. + * @author Christian Kohlschütter + */ +final class MapValueSet implements Set { + private final Map map; + private final ValueSupplier<@NonNull V> valueSupplier; + private final V removedSentinel; + + @SuppressWarnings("unchecked") + MapValueSet(Map map, ValueSupplier<@NonNull V> valueSupplier, V removedSentinel) { + this.valueSupplier = Objects.requireNonNull(valueSupplier); + this.removedSentinel = removedSentinel; + this.map = (Map) map; + } + + @FunctionalInterface + interface ValueSupplier { + V supplyValue(); + } + + /** + * Marks the given element as "removed"; this may actually add an element to the underlying map. + *

+ * Depending on the "removed" sentinel, the key may be added (if value is non-null but the map + * does not yet contain the key), modified (value is non-null, and the map has a different value + * for the key), or removed (if value is null). + * + * @param elem The element to remove. + */ + public void markRemoved(T elem) { + if (removedSentinel == null) { + map.remove(elem); + } else { + map.put(elem, removedSentinel); + } + } + + /** + * Sets all entries in the backing map to the "removed" sentinel, or removes them all if that + * value is {@code null}. + */ + public void markAllRemoved() { + if (removedSentinel == null) { + map.clear(); + } else { + for (Map.Entry en : map.entrySet()) { + en.setValue(removedSentinel); + } + } + } + + private @NonNull V getValue() { + return Objects.requireNonNull(valueSupplier.supplyValue()); + } + + @Override + public int size() { + V val = getValue(); + if (val.equals(removedSentinel)) { + return 0; + } + + int size = 0; + for (Map.Entry en : map.entrySet()) { + if (val.equals(en.getValue())) { + size++; + } + } + return size; + } + + @Override + public boolean isEmpty() { + V val = getValue(); + if (val.equals(removedSentinel)) { + return true; + } + + for (Map.Entry en : map.entrySet()) { + if (val.equals(en.getValue())) { + return false; + } + } + return true; + } + + private boolean isDefinitelyEmpty() { + return getValue().equals(removedSentinel); + } + + @Override + public boolean contains(Object o) { + if (isDefinitelyEmpty()) { + return false; + } + return getValue().equals(map.get(o)); + } + + @Override + public Iterator iterator() { + if (isDefinitelyEmpty()) { + return Collections.emptyIterator(); + } + + Iterator> mapit = map.entrySet().iterator(); + + V val = getValue(); + + return new Iterator() { + Map.Entry nextObj = null; + Map.Entry currentObj = null; + + @Override + public boolean hasNext() { + if (nextObj != null) { + return true; + } + while (mapit.hasNext()) { + Map.Entry en = mapit.next(); + if (val.equals(en.getValue())) { + nextObj = en; + return true; + } + } + return false; + } + + @Override + public T next() { + currentObj = null; + if (nextObj == null) { + if (!hasNext()) { + throw new NoSuchElementException(); + } + } + T next = nextObj.getKey(); + if (val.equals(nextObj.getValue())) { + currentObj = nextObj; + nextObj = null; + return next; + } else { + throw new ConcurrentModificationException(); + } + } + + @Override + public void remove() { + if (currentObj == null) { + throw new IllegalStateException(); + } + markRemoved(currentObj.getKey()); + currentObj = null; + } + }; + } + + @Override + @SuppressWarnings("PMD.OptimizableToArrayCall") + public Object[] toArray() { + return toArray(new Object[size()]); + } + + @SuppressWarnings({"unchecked", "null"}) + @Override + public E[] toArray(E[] a) { + int size = size(); + + if (a.length < size) { + return toArray((E[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), + size)); + } + + int i = 0; + for (T elem : this) { + a[i++] = (E) elem; + } + if (i < a.length) { + a[i] = null; + } + + return a; + } + + /** + * Updates an already-existing entry in the backing map to the current value (obtained via + * {@link #getValue()}), thereby adding it to the set. + * + * @param e The entry to update. + */ + public boolean update(T e) { + if (map.containsKey(e)) { + map.put(e, getValue()); + return true; + } else { + return false; + } + } + + /** + * Adds an entry to the set, adding it to the backing map if necessary. + */ + @Override + public boolean add(T e) { + if (contains(e)) { + return false; + } else if (update(e)) { + return true; + } else { + map.put(e, getValue()); + return true; + } + } + + @SuppressWarnings("unchecked") + @Override + public boolean remove(Object o) { + if (isDefinitelyEmpty() || !map.containsKey(o)) { + return false; + } + + markRemoved((T) o); + return true; + } + + @Override + public boolean containsAll(Collection c) { + if (isDefinitelyEmpty()) { + return c.isEmpty(); + } + for (Object obj : c) { + if (!contains(obj)) { + return false; + } + } + return true; + } + + @Override + public boolean addAll(Collection c) { + boolean changed = false; + for (T elem : c) { + changed |= add(elem); + } + return changed; + } + + @Override + public boolean retainAll(Collection c) { + boolean changed = false; + for (Iterator it = iterator(); it.hasNext();) { + T elem = it.next(); + if (!c.contains(elem)) { + it.remove(); + changed = true; + } + } + return changed; + } + + @Override + public boolean removeAll(Collection c) { + if (isDefinitelyEmpty()) { + return false; + } + boolean changed = false; + for (Object obj : c) { + changed |= remove(obj); + } + return changed; + } + + /** + * Marks all entries in the backing map that are currently considered contained in this set as + * removed; see {@link #markAllRemoved()} for an unoptimized version that affects all keys. + * + * @see #markAllRemoved() + */ + @Override + public void clear() { + V val = getValue(); + if (val.equals(removedSentinel)) { + return; + } + + for (Map.Entry en : map.entrySet()) { + if (val.equals(en.getValue())) { + markRemoved(en.getKey()); + } + } + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/NamedInteger.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/NamedInteger.java index 647a32914..0483afe71 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/NamedInteger.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/NamedInteger.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,9 +27,9 @@ /** * A "named integer", usually used for constants. - * + * * See the concrete implementations for usage. - * + * * @author Christian Kohlschütter */ @NonNullByDefault @@ -57,7 +57,7 @@ public interface HasOfValue { /** * Creates a new {@link NamedInteger} instance, without actually naming it. A name of "UNDEFINED" * is used. - * + * * @param id The value. */ protected NamedInteger(int id) { @@ -66,7 +66,7 @@ protected NamedInteger(int id) { /** * Creates a new {@link NamedInteger} instance. - * + * * @param name The name. * @param id The value. */ @@ -77,7 +77,7 @@ protected NamedInteger(String name, int id) { /** * Returns the name. - * + * * @return The name. */ public final String name() { @@ -86,7 +86,7 @@ public final String name() { /** * Returns the value. - * + * * @return The value. */ public final int value() { @@ -118,7 +118,7 @@ public final boolean equals(@Nullable Object obj) { /** * Ensures that the {@code VALUES} array is configured correctly. - * + * * @param The instance type. * @param values The {@code VALUES} array. * @return The verified {@code VALUES} array. @@ -135,14 +135,14 @@ protected static final T[] init(T[] values) { /** * Constructor for "undefined" values. - * + * * @param The instance type. */ @FunctionalInterface protected interface UndefinedValueConstructor { /** * Creates a new "undefined" value instance. - * + * * @param id The value. * @return The instance. */ @@ -151,7 +151,7 @@ protected interface UndefinedValueConstructor { /** * Returns an instance given an integer value. - * + * * @param The instance type. * @param values The {@code VALUES} array. * @param constr The constructor for undefined values. diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/NamedIntegerBitmask.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/NamedIntegerBitmask.java index af525ded1..92b23a637 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/NamedIntegerBitmask.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/NamedIntegerBitmask.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -28,7 +29,7 @@ /** * Describes a 32-bit bitmask that supports named flags. - * + * * @param The subclass's type itself. * @author Christian Kohlschütter */ @@ -49,7 +50,7 @@ public abstract class NamedIntegerBitmask> impl /** * Creates a new named flag. - * + * * @param name The name of the flag / flag set. * @param flags The flag value. */ @@ -60,7 +61,7 @@ protected NamedIntegerBitmask(@Nullable String name, int flags) { /** * Returns the name of the flag / flag set. - * + * * @return The name. */ public final String name() { @@ -69,7 +70,7 @@ public final String name() { /** * Returns the value of the flag / flag set. - * + * * @return The value. */ public final int value() { @@ -78,12 +79,12 @@ public final int value() { /** * Checks if the given flag is set. - * + * * @param flag The flag to check. * @return {@code true} iff set. */ public final boolean hasFlag(T flag) { - int v = flag.value(); + int v = Objects.requireNonNull(flag).value(); return (this.flags & v) == v; } @@ -95,21 +96,22 @@ public final String toString() { /** * Combines two flags / flag sets (use this to implement * {@link #combineWith(NamedIntegerBitmask)}). - * + * * @param allFlags The array of all defined flags, expected "none". * @param flagsNone The "none" flag set. * @param constr The constructor. * @param other The other flag / flag set to merge. * @return An instance combining both. */ - @SuppressWarnings("PMD.ShortMethodName") - protected final T combineWith(T[] allFlags, T flagsNone, Constructor constr, T other) { + @SuppressWarnings({"PMD.ShortMethodName", "null"}) + protected final T combineWith(T[] allFlags, T flagsNone, Constructor<@NonNull T> constr, + T other) { return resolve(allFlags, flagsNone, constr, value() | other.value()); } /** * Combines two flags / flag sets. - * + * * @param other The other flag / flag set. * @return An instance combining both. */ @@ -118,14 +120,14 @@ protected final T combineWith(T[] allFlags, T flagsNone, Constructor constr, /** * Creates a new instance. - * + * * @param This type. */ @FunctionalInterface protected interface Constructor> { /** * Creates a new instance. - * + * * @param name The name. * @param flags The flag value. * @return The instance. @@ -135,7 +137,7 @@ protected interface Constructor> { /** * Returns a {@link NamedIntegerBitmask} instance given a flag value. - * + * * @param The subclass's type itself. * @param allFlags The array of all defined flags, expected "none". * @param flagsNone The "none" flag set. @@ -167,7 +169,7 @@ protected static final > T resolve(T[] allFlags /** * Returns a {@link NamedIntegerBitmask} instance given a series of flags. - * + * * @param The subclass's type itself. * @param allFlags The array of all defined flags, expected "none". * @param flagsNone The "none" flag set. diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/NativeLibraryLoader.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/NativeLibraryLoader.java index eb225b1f6..fb681e3d6 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/NativeLibraryLoader.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/NativeLibraryLoader.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,11 +24,14 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.URL; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; @@ -41,13 +44,17 @@ final class NativeLibraryLoader implements Closeable { private static final String PROP_LIBRARY_TMPDIR = "org.newsclub.net.unix.library.tmpdir"; private static final File TEMP_DIR; + private static final String OS_NAME_SIMPLIFIED = lookupArchProperty("os.name", "UnknownOS"); + private static final List ARCHITECTURE_AND_OS = architectureAndOS(); private static final String LIBRARY_NAME = "junixsocket-native"; - private static boolean loaded = false; + private static final AtomicBoolean LOADED = new AtomicBoolean(false); + private static final boolean IS_ANDROID = checkAndroid(); static { - String dir = System.getProperty(PROP_LIBRARY_TMPDIR, null); + String dir = System.getProperty(PROP_LIBRARY_TMPDIR, System.getProperty("java.io.tmpdir", + null)); TEMP_DIR = (dir == null) ? null : new File(dir); } @@ -56,7 +63,7 @@ final class NativeLibraryLoader implements Closeable { /** * Returns the temporary directory where the native library is extracted to; debugging only. - * + * * @return The temporary directory. */ static File tempDir() { @@ -75,6 +82,12 @@ private List tryProviderClass(String providerClassname, String public static String getJunixsocketVersion() throws IOException { // NOTE: This can't easily be tested from within the junixsocket-common Maven build + + String v = BuildProperties.getBuildProperties().get("git.build.version"); + if (v != null && !v.startsWith("$")) { + return v; + } + return getArtifactVersion(AFSocket.class, "junixsocket-common"); } @@ -120,7 +133,7 @@ public String toString() { private static final class StandardLibraryCandidate extends LibraryCandidate { StandardLibraryCandidate(String version) { - super(version == null ? null : LIBRARY_NAME + "-" + version); + super(version == null ? LIBRARY_NAME : LIBRARY_NAME + "-" + version); } @Override @@ -141,56 +154,149 @@ public void close() { public String toString() { return super.toString() + "(standard library path)"; } - } private static final class ClasspathLibraryCandidate extends LibraryCandidate { private final String artifactName; - private final InputStream libraryIn; + private final URL library; private final String path; ClasspathLibraryCandidate(String artifactName, String libraryNameAndVersion, String path, - InputStream libraryIn) { + URL library) { super(libraryNameAndVersion); this.artifactName = artifactName; this.path = path; - this.libraryIn = libraryIn; + this.library = library; + } + + /** + * Even though we ask the JVM to delete the library file upon VM exit, this may not be honored + * in all cases (crash, Windows, etc.) + * + * Therefore, we attempt to delete these files whenever another JVM using junixsocket starts up. + * This is simplified by keeping empty marker files next to the temporary shared library file. + * + * @param libDir The directory to check. + */ + private void deleteLibTmpDelFiles(File libDir) { + if (libDir == null) { + try { + File tempFile = File.createTempFile("libtmp", ".del"); + libDir = tempFile.getParentFile(); + tryDelete(tempFile); + } catch (IOException e) { + return; + } + } + File[] filesToDelete = libDir.listFiles((File f) -> { + if (!f.isFile()) { + return false; + } + String name = f.getName(); + return name.startsWith("libtmp") && name.endsWith(".del"); + }); + if (filesToDelete == null || filesToDelete.length == 0) { + return; + } + + for (File f : filesToDelete) { + tryDelete(f); + String n = f.getName(); + n = n.substring(0, n.length() - ".del".length()); + File libFile = new File(f.getParentFile(), n); + tryDelete(libFile); + } } @Override + @SuppressWarnings("PMD.CognitiveComplexity") synchronized String load() throws IOException, LinkageError { if (libraryNameAndVersion == null) { return null; } - File libFile; - try { - libFile = createTempFile("libtmp", System.mapLibraryName(libraryNameAndVersion)); - try (OutputStream out = new FileOutputStream(libFile)) { // NOPMD UseTryWithResources - byte[] buf = new byte[4096]; - int read; - while ((read = libraryIn.read(buf)) >= 0) { - out.write(buf, 0, read); + + File libDir = TEMP_DIR; + File userHomeDir = new File(System.getProperty("user.home", ".")); + File userDirOrNull = new File(System.getProperty("user.dir", ".")); + if (userHomeDir.equals(userDirOrNull)) { + userDirOrNull = null; + } + + deleteLibTmpDelFiles(libDir); + deleteLibTmpDelFiles(userHomeDir); + if (userDirOrNull != null) { + deleteLibTmpDelFiles(userDirOrNull); + } + + for (int attempt = 0; attempt < 3; attempt++) { + File libFile; + try { + libFile = File.createTempFile("libtmp", System.mapLibraryName(libraryNameAndVersion), + libDir); + try (InputStream libraryIn = library.openStream(); + OutputStream out = new FileOutputStream(libFile)) { // NOPMD UseTryWithResources + byte[] buf = new byte[4096]; + int read; + while ((read = libraryIn.read(buf)) >= 0) { + out.write(buf, 0, read); + } + } + } catch (IOException e) { + throw e; + } + + try { + System.load(libFile.getAbsolutePath()); + } catch (UnsatisfiedLinkError e) { + // Operation not permitted; permission denied; EPERM... + // -> tmp directory may be mounted with "noexec", try loading from user.home, user.dir + + switch (attempt) { + case 0: + libDir = userHomeDir; + break; + case 1: + if (userDirOrNull != null) { + libDir = userDirOrNull; + break; + } + // fall-through + default: + throw e; } + + continue; } finally { - libraryIn.close(); + if (!libFile.delete() && libFile.exists()) { + libFile.deleteOnExit(); + + File markerFile = new File(libFile.getParentFile(), libFile.getName() + ".del"); + try { + Files.createFile(markerFile.toPath()); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + if (!libFile.exists() || libFile.delete()) { + tryDelete(markerFile); + } + })); + } catch (IOException | UnsupportedOperationException e) { + // ignore + } + } } - } catch (IOException e) { - throw e; - } - System.load(libFile.getAbsolutePath()); - if (!libFile.delete()) { - libFile.deleteOnExit(); + + // If we reach this, then we were able to load the library + break; // NOPMD.AvoidBranchingStatementAsLastInLoop } return artifactName + "/" + libraryNameAndVersion; } + @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE") + private static void tryDelete(File f) { + f.delete(); // NOPMD + } + @Override public void close() { - try { - libraryIn.close(); - } catch (IOException e) { - // ignore - } } @Override @@ -205,11 +311,10 @@ private synchronized void setLoaded(String library) { @SuppressFBWarnings("THROWS_METHOD_THROWS_RUNTIMEEXCEPTION") private static synchronized void setLoaded0(String library) { - if (!loaded) { - loaded = true; + if (LOADED.compareAndSet(false, true)) { + NativeUnixSocket.setLoaded(true); AFSocket.loadedLibrary = library; try { - NativeUnixSocket.initPre(); NativeUnixSocket.init(); } catch (RuntimeException e) { throw e; @@ -223,7 +328,18 @@ private Throwable loadLibraryOverride() { String libraryOverride = System.getProperty(PROP_LIBRARY_OVERRIDE, ""); String libraryOverrideForce = System.getProperty(PROP_LIBRARY_OVERRIDE_FORCE, "false"); - if (libraryOverride.isEmpty() && libraryOverrideForce.startsWith("/")) { + boolean overrideIsAbsolute; + try { + if (libraryOverrideForce.length() <= 5) { // reasonable simplification + overrideIsAbsolute = false; + } else { + overrideIsAbsolute = new File(libraryOverrideForce).isAbsolute(); + } + } catch (Exception e) { + overrideIsAbsolute = false; + e.printStackTrace(); // NOPMD + } + if (libraryOverride.isEmpty() && overrideIsAbsolute) { libraryOverride = libraryOverrideForce; libraryOverrideForce = "true"; } @@ -234,7 +350,7 @@ private Throwable loadLibraryOverride() { setLoaded(libraryOverride); return null; } catch (Exception | LinkageError e) { - if (Boolean.valueOf(libraryOverrideForce)) { + if (Boolean.parseBoolean(libraryOverrideForce)) { throw e; } return e; @@ -254,15 +370,16 @@ private static Object loadLibrarySyncMonitor() { } } - // NOPMD @SuppressWarnings("null") public synchronized void loadLibrary() { synchronized (loadLibrarySyncMonitor()) { // NOPMD We want to lock this class' classloader. - if (loaded) { + if (LOADED.get()) { // Already loaded return; } + NativeUnixSocket.initPre(); + // set -Dorg.newsclub.net.unix.library.override.force=provided to assume that // we already have loaded the library via System.load, etc. if ("provided".equals(System.getProperty(PROP_LIBRARY_OVERRIDE_FORCE, ""))) { @@ -270,7 +387,19 @@ public synchronized void loadLibrary() { return; } - if (Boolean.valueOf(System.getProperty(PROP_LIBRARY_DISABLE, "false"))) { + boolean provided = false; + try { + NativeUnixSocket.noop(); + provided = true; + } catch (UnsatisfiedLinkError | Exception e) { + // expected unless we manually loaded the library + } + if (provided) { + setLoaded("provided"); + return; + } + + if (Boolean.parseBoolean(System.getProperty(PROP_LIBRARY_DISABLE, "false"))) { throw initCantLoadLibraryError(Collections.singletonList(new UnsupportedOperationException( "junixsocket disabled by System.property " + PROP_LIBRARY_DISABLE))); } @@ -330,11 +459,14 @@ private UnsatisfiedLinkError initCantLoadLibraryError(List suppressed private List initLibraryCandidates(List suppressedThrowables) { List candidates = new ArrayList<>(); try { - candidates.add(new StandardLibraryCandidate(getArtifactVersion(getClass(), - "junixsocket-common", "junixsocket-core"))); + String version = getArtifactVersion(getClass(), "junixsocket-common", "junixsocket-core"); + if (version != null) { + candidates.add(new StandardLibraryCandidate(version)); + } } catch (Exception e) { suppressedThrowables.add(e); } + try { candidates.addAll(tryProviderClass("org.newsclub.lib.junixsocket.custom.NarMetadata", "junixsocket-native-custom")); @@ -348,25 +480,85 @@ private List initLibraryCandidates(List suppressedT suppressedThrowables.add(e); } + candidates.add(new StandardLibraryCandidate(null)); + return candidates; } + private static String lookupArchProperty(String key, String defaultVal) { + return System.getProperty(key, defaultVal).replaceAll("[ /\\\\'\";:\\$]", ""); + } + private static List architectureAndOS() { - String arch = System.getProperty("os.arch", "UnknownArch").replaceAll("[ /\\\\'\";:\\$]", ""); - String osName = System.getProperty("os.name", "UnknownOS").replaceAll("[ /\\\\'\";:\\$]", ""); + String arch = lookupArchProperty("os.arch", "UnknownArch"); List list = new ArrayList<>(); - list.add(arch + "-" + osName); - if (osName.startsWith("Windows") && !"Windows10".equals(osName)) { + if (IS_ANDROID) { + // Android identifies itself as os.name="Linux" + // let's probe for an Android-specific library first + list.add(arch + "-Android"); + } + list.add(arch + "-" + OS_NAME_SIMPLIFIED); + if (OS_NAME_SIMPLIFIED.startsWith("Windows") && !"Windows10".equals(OS_NAME_SIMPLIFIED)) { list.add(arch + "-" + "Windows10"); } + if ("MacOSX".equals(OS_NAME_SIMPLIFIED) && "x86_64".equals(arch)) { + list.add("aarch64-MacOSX"); // Rosetta 2 + } + return list; } + private static boolean checkAndroid() { + String vmName = lookupArchProperty("java.vm.name", "UnknownVM"); + String vmSpecVendor = lookupArchProperty("java.vm.specification.vendor", + "UnknownSpecificationVendor"); + + return ("Dalvik".equals(vmName) || vmSpecVendor.contains("Android")); + } + + static boolean isAndroid() { + return IS_ANDROID; + } + + static List getArchitectureAndOS() { + return ARCHITECTURE_AND_OS; + } + + private static URL validateResourceURL(URL url) { + if (url == null) { + return null; + } + try (InputStream unused = url.openStream()) { + return url; + } catch (IOException e) { + return null; + } + } + + private static String mapLibraryName(String libraryNameAndVersion) { + String mappedName = System.mapLibraryName(libraryNameAndVersion); + if (mappedName.endsWith(".so")) { + // https://github.com/eclipse-openj9/openj9/issues/9788 + // Many thanks to Fabrice Bourquin for finding this issue! + switch (OS_NAME_SIMPLIFIED) { + case "AIX": + mappedName = mappedName.substring(0, mappedName.length() - 3) + ".a"; + break; + case "OS400": + mappedName = mappedName.substring(0, mappedName.length() - 3) + ".srvpgm"; + break; + default: + break; + } + } + return mappedName; + } + private List findLibraryCandidates(String artifactName, String libraryNameAndVersion, Class providerClass) { - String mappedName = System.mapLibraryName(libraryNameAndVersion); + String mappedName = mapLibraryName(libraryNameAndVersion); String[] prefixes = mappedName.startsWith("lib") ? new String[] {""} : new String[] {"", "lib"}; @@ -380,11 +572,11 @@ private List findLibraryCandidates(String artifactName, for (String prefix : prefixes) { String path = "/lib/" + archOs + "-" + compiler + "/jni/" + prefix + mappedName; - InputStream in; + URL url; - in = providerClass.getResourceAsStream(path); - if (in != null) { - list.add(new ClasspathLibraryCandidate(artifactName, libraryNameAndVersion, path, in)); + url = validateResourceURL(providerClass.getResource(path)); + if (url != null) { + list.add(new ClasspathLibraryCandidate(artifactName, libraryNameAndVersion, path, url)); } // NOTE: we have to try .nodeps version _after_ trying the properly linked one. @@ -392,10 +584,10 @@ private List findLibraryCandidates(String artifactName, // with a "symbol lookup error" String nodepsPath = nodepsPath(path); if (nodepsPath != null) { - in = providerClass.getResourceAsStream(nodepsPath); - if (in != null) { + url = validateResourceURL(providerClass.getResource(nodepsPath)); + if (url != null) { list.add(new ClasspathLibraryCandidate(artifactName, libraryNameAndVersion, - nodepsPath, in)); + nodepsPath, url)); } } } @@ -413,10 +605,6 @@ private String nodepsPath(String path) { } } - private static File createTempFile(String prefix, String suffix) throws IOException { - return File.createTempFile(prefix, suffix, TEMP_DIR); - } - @Override public void close() { } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/NativeUnixSocket.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/NativeUnixSocket.java index 18ef5de11..72716ea77 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/NativeUnixSocket.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/NativeUnixSocket.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import java.io.FileDescriptor; import java.io.IOException; import java.lang.ProcessBuilder.Redirect; +import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; @@ -28,6 +29,7 @@ import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.spi.AbstractSelectableChannel; +import java.util.concurrent.atomic.AtomicBoolean; import org.newsclub.net.unix.AFSelector.PollFd; @@ -36,18 +38,21 @@ /** * JNI connector to native JNI C code. - * + * * @author Christian Kohlschütter */ final class NativeUnixSocket { - private static boolean loaded; + private static final AtomicBoolean LOADED = new AtomicBoolean(false); + static final int DOMAIN_GENERIC = -1; static final int DOMAIN_UNIX = 1; static final int DOMAIN_TIPC = 30; static final int DOMAIN_VSOCK = 40; + static final int DOMAIN_SYSTEM = 32; static final int SOCK_STREAM = 1; static final int SOCK_DGRAM = 2; + static final int SOCK_RAW = 3; static final int SOCK_RDM = 4; static final int SOCK_SEQPACKET = 5; @@ -69,32 +74,44 @@ final class NativeUnixSocket { static final int SOCKETSTATUS_BOUND = 1; static final int SOCKETSTATUS_CONNECTED = 2; + @SuppressWarnings("StaticAssignmentOfThrowable" /* errorprone */) private static Throwable initError = null; - @ExcludeFromCodeCoverageGeneratedReport + static final int SHUT_RD = 0; + static final int SHUT_WR = 1; + static final int SHUT_RD_WR = 2; + + @ExcludeFromCodeCoverageGeneratedReport(reason = "unreachable") private NativeUnixSocket() { throw new UnsupportedOperationException("No instances"); } static { + boolean loadSuccessful = false; try (NativeLibraryLoader nll = new NativeLibraryLoader()) { nll.loadLibrary(); - loaded = true; + loadSuccessful = true; } catch (RuntimeException | Error e) { initError = e; - e.printStackTrace(); // keep + StackTraceUtil.printStackTraceSevere(e); + } finally { + setLoaded(loadSuccessful); } + AFAddressFamily.registerAddressFamily("generic", NativeUnixSocket.DOMAIN_GENERIC, + "org.newsclub.net.unix.AFGenericSocketAddress"); AFAddressFamily.registerAddressFamily("un", NativeUnixSocket.DOMAIN_UNIX, "org.newsclub.net.unix.AFUNIXSocketAddress"); AFAddressFamily.registerAddressFamily("tipc", NativeUnixSocket.DOMAIN_TIPC, "org.newsclub.net.unix.AFTIPCSocketAddress"); AFAddressFamily.registerAddressFamily("vsock", NativeUnixSocket.DOMAIN_VSOCK, "org.newsclub.net.unix.AFVSOCKSocketAddress"); + AFAddressFamily.registerAddressFamily("system", NativeUnixSocket.DOMAIN_SYSTEM, + "org.newsclub.net.unix.AFSYSTEMSocketAddress"); } static boolean isLoaded() { - return loaded; + return LOADED.get(); } static void ensureSupported() throws UnsupportedOperationException { @@ -106,7 +123,7 @@ static void ensureSupported() throws UnsupportedOperationException { static UnsupportedOperationException unsupportedException() { if (!isLoaded()) { return (UnsupportedOperationException) new UnsupportedOperationException( - "junixsocket is not supported on this platform").initCause(initError); + "junixsocket may not be fully supported on this platform").initCause(initError); } else { return null; } @@ -120,6 +137,14 @@ static void initPre() { // in some environments, JNI FindClass won't find these classes unless we resolve them first tryResolveClass(AbstractSelectableChannel.class.getName()); tryResolveClass("java.lang.ProcessBuilder$RedirectPipeImpl"); + tryResolveClass(InetSocketAddress.class.getName()); + tryResolveClass(InvalidArgumentSocketException.class.getName()); + tryResolveClass(AddressUnavailableSocketException.class.getName()); + tryResolveClass(OperationNotSupportedSocketException.class.getName()); + tryResolveClass(NoSuchDeviceSocketException.class.getName()); + tryResolveClass(BrokenPipeSocketException.class.getName()); + tryResolveClass(ConnectionResetSocketException.class.getName()); + tryResolveClass(SocketClosedException.class.getName()); } private static void tryResolveClass(String className) { @@ -136,6 +161,13 @@ private static void tryResolveClass(String className) { @SuppressFBWarnings("THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION") static native void destroy() throws Exception; + /** + * Can be used to check (without side-effects) if the library has been loaded. + * + * Terminates normally if so; throws {@link UnsatisfiedLinkError} if not. + */ + static native void noop(); + static native int capabilities(); static native byte[] sockname(int domain, FileDescriptor fd, boolean peer); @@ -157,11 +189,14 @@ static native boolean connect(ByteBuffer sockaddr, int sockaddrLen, FileDescript static native int socketStatus(FileDescriptor fd) throws IOException; + static native FileDescriptor duplicate(FileDescriptor fdSource, FileDescriptor fdTarget) + throws IOException; + static native Class primaryType(FileDescriptor fd) throws IOException; /** * Reads data from an {@link AFSocketImpl}. - * + * * @param fd The corresponding file descriptor. * @param buf The buffer to read into, or {@code null} if a single byte should be read. * @param off The buffer offset. @@ -177,7 +212,7 @@ static native int read(FileDescriptor fd, byte[] buf, int off, int len, int opti /** * Writes data to an {@link AFSocketImpl}. - * + * * @param fd The corresponding file descriptor. * @param buf The buffer to write from, or {@code null} if a single byte should be written. * @param off The buffer offset, or the byte to write if {@code buf} is {@code null}. @@ -232,7 +267,8 @@ static native void initServerImpl(ServerSocket serverSocket, AFSocketImpl imp static native void copyFileDescriptor(FileDescriptor source, FileDescriptor target) throws IOException; - static native void attachCloseable(FileDescriptor fdsec, Closeable closeable); + static native void attachCloseable(FileDescriptor fdesc, Closeable closeable) + throws SocketException; static native int maxAddressLength(); @@ -268,6 +304,15 @@ static native boolean initPipe(FileDescriptor source, FileDescriptor sink, boole static native void configureBlocking(FileDescriptor fd, boolean blocking) throws IOException; + /** + * Checks if the given file descriptor describes a blocking socket. + * + * @param fd The file descriptor to check + * @return 0 = non-blocking, 1 = blocking, 2 = indeterminate (needs reconfiguration) + * @throws IOException on error. + */ + static native int checkBlocking(FileDescriptor fd) throws IOException; + static native void socketPair(int domain, int type, FileDescriptor fd, FileDescriptor fd2); static native Redirect initRedirect(FileDescriptor fd); @@ -285,4 +330,10 @@ static native boolean initPipe(FileDescriptor source, FileDescriptor sink, boole static native int sockTypeToNative(int type) throws IOException; static native int vsockGetLocalCID() throws IOException; + + static native int systemResolveCtlId(FileDescriptor fd, String ctlName) throws IOException; + + static void setLoaded(boolean successful) { + LOADED.compareAndSet(false, successful); + } } diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/NoSuchDeviceSocketException.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/NoSuchDeviceSocketException.java new file mode 100644 index 000000000..473043e66 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/NoSuchDeviceSocketException.java @@ -0,0 +1,46 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.net.SocketException; + +/** + * A {@link SocketException} that may be thrown upon some "no such device" condition from native + * code (e.g., ENODEV is returned). + * + * @author Christian Kohlschütter + */ +public class NoSuchDeviceSocketException extends InvalidSocketException { + private static final long serialVersionUID = 1L; + + /** + * Constructs a new {@link NoSuchDeviceSocketException}. + */ + public NoSuchDeviceSocketException() { + super(); + } + + /** + * Constructs a new {@link NoSuchDeviceSocketException}. + * + * @param msg The error message. + */ + public NoSuchDeviceSocketException(String msg) { + super(msg); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/NotBoundSocketException.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/NotBoundSocketException.java new file mode 100644 index 000000000..3a1f9b35b --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/NotBoundSocketException.java @@ -0,0 +1,37 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.net.SocketException; + +/** + * A {@link SocketException} indicating that a socket was not bound when attempting to read or + * write. + * + * @author Christian Kohlschütter + */ +public final class NotBoundSocketException extends SocketException { + private static final long serialVersionUID = 1L; + + /** + * Constructs a new {@link NotBoundSocketException}. + */ + public NotBoundSocketException() { + super(); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/NotConnectedSocketException.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/NotConnectedSocketException.java new file mode 100644 index 000000000..3aa308be1 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/NotConnectedSocketException.java @@ -0,0 +1,46 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.net.SocketException; + +/** + * A {@link SocketException} indicating that a socket was not connected when attempting to read or + * write. + * + * @author Christian Kohlschütter + */ +public final class NotConnectedSocketException extends SocketException { + private static final long serialVersionUID = 1L; + + /** + * Constructs a new {@link NotConnectedSocketException}. + */ + public NotConnectedSocketException() { + super(); + } + + /** + * Constructs a new {@link NotConnectedSocketException}. + * + * @param msg The error message. + */ + public NotConnectedSocketException(String msg) { + super(msg); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/OperationNotSupportedSocketException.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/OperationNotSupportedSocketException.java index f97dce16c..515036eeb 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/OperationNotSupportedSocketException.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/OperationNotSupportedSocketException.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,9 @@ /** * A {@link SocketException} that may be thrown upon some "unsupported operation" condition from - * native code (e.g., EOPNOTSUPP is returned). - * + * native code (e.g., ENOTSUP, EOPNOTSUPP, EPROTONOSUPPORT, ESOCKTNOSUPPORT, EPFNOSUPPORT, + * EAFNOSUPPORT is returned). + * * @author Christian Kohlschütter */ public class OperationNotSupportedSocketException extends InvalidSocketException { @@ -37,7 +38,7 @@ public OperationNotSupportedSocketException() { /** * Constructs a new {@link OperationNotSupportedSocketException}. - * + * * @param msg The error message. */ public OperationNotSupportedSocketException(String msg) { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/PathUtil.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/PathUtil.java new file mode 100644 index 000000000..840a948a5 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/PathUtil.java @@ -0,0 +1,51 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Path; + +import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; + +/** + * Some {@link Path}-related helper methods. + * + * @author Christian Kohlschütter + */ +@IgnoreJRERequirement // see src/main/java8 +final class PathUtil { + private PathUtil() { + throw new IllegalStateException("No instances"); + } + + /** + * Checks if the given path is in the default file system. + * + * @param p The path. + * @return {@code true} if so. + */ + @SuppressWarnings("PMD.CompareObjectsWithEquals") + static boolean isPathInDefaultFileSystem(Path p) { + FileSystem fs = p.getFileSystem(); + if (fs != FileSystems.getDefault() || fs.getClass().getModule() != Object.class.getModule()) { + return false; + } + return true; + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/RAFChannelProvider.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/RAFChannelProvider.java index 21b9b26ef..a116ca53c 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/RAFChannelProvider.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/RAFChannelProvider.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ /** * Hack to get a readable AND writable {@link FileChannel} for a {@link FileDescriptor}. - * + * * @author Christian Kohlschütter */ final class RAFChannelProvider extends RandomAccessFile implements FileDescriptorAccess { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/SelectorProviderShim.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/SelectorProviderShim.java new file mode 100644 index 000000000..ec7eb0c33 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/SelectorProviderShim.java @@ -0,0 +1,23 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.nio.channels.spi.SelectorProvider; + +abstract class SelectorProviderShim extends SelectorProvider { // NOPMD.AbstractClassWithoutAnyMethod +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/SentinelSocketAddress.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/SentinelSocketAddress.java index 7d0d6e91a..96bc9a909 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/SentinelSocketAddress.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/SentinelSocketAddress.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketAddressFilter.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketAddressFilter.java index 5c82039fa..4e58310db 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketAddressFilter.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketAddressFilter.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ /** * A filter that takes a {@link SocketAddress}, and potentially changes it, or throws an exception * if certain criteria are met. - * + * * @author Christian Kohlschütter */ @FunctionalInterface @@ -31,7 +31,7 @@ public interface SocketAddressFilter { /** * Applies the filter on the given address. - * + * * @param address The address. * @return The address itself or a changed address. * @throws IOException on error or if a certain error condition is desired. diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketAddressUtil.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketAddressUtil.java new file mode 100644 index 000000000..de27979b4 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketAddressUtil.java @@ -0,0 +1,70 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.UnixDomainSocketAddress; + +import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; + +/** + * {@link SocketAddress}-related helper methods. + * + * @author Christian Kohlschütter + */ +@IgnoreJRERequirement // see src/main/java15 +final class SocketAddressUtil { + private SocketAddressUtil() { + throw new IllegalStateException("No instances"); + } + + /** + * Try to convert a {@link SocketAddress} that is not a {@link AFSocketAddress} to one that is. + * + * @param address The address. + * @return A supplier for the given address, or {@code null}. + */ + static AFSupplier supplyAFSocketAddress(SocketAddress address) { + return supplyAFUNIXSocketAddress(address); + } + + /** + * Try to convert a {@link SocketAddress} that is not a {@link AFUNIXSocketAddress} to one that + * is. + * + * @param address The address. + * @return A supplier for the given address, or {@code null}. + */ + static AFSupplier supplyAFUNIXSocketAddress(SocketAddress address) { + if (address instanceof UnixDomainSocketAddress) { + UnixDomainSocketAddress udsa = (UnixDomainSocketAddress) address; + + return () -> { + try { + return AFUNIXSocketAddress.of(udsa.getPath()); + } catch (SocketException e) { + e.printStackTrace(); + return null; + } + }; + } else { + return null; + } + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketClosedByInterruptException.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketClosedByInterruptException.java new file mode 100644 index 000000000..b68877ce5 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketClosedByInterruptException.java @@ -0,0 +1,63 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.Closeable; +import java.io.IOException; +import java.net.SocketException; +import java.nio.channels.ClosedByInterruptException; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * A {@link SocketException} indicating that a socket was closed by interrupt. + * + * @author Christian Kohlschütter + */ +final class SocketClosedByInterruptException extends SocketClosedException { + private static final long serialVersionUID = 1L; + + /** + * Constructs a new {@link SocketClosedByInterruptException}. + */ + private SocketClosedByInterruptException() { + super("Closed by interrupt"); + } + + static SocketClosedByInterruptException newInstanceAndClose(Closeable closeable) { + Throwable suppressed = null; + try { + if (closeable != null) { + closeable.close(); + } + } catch (RuntimeException | IOException e) { + suppressed = e; + } + SocketClosedByInterruptException exc = new SocketClosedByInterruptException(); + if (suppressed != null) { + exc.addSuppressed(suppressed); + } + return exc; + } + + public @NonNull ClosedByInterruptException asClosedByInterruptException() { + ClosedByInterruptException exc = new ClosedByInterruptException(); + exc.initCause(this); + return exc; + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketClosedException.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketClosedException.java index afce7ad58..183821cf3 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketClosedException.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketClosedException.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,10 +21,10 @@ /** * A {@link SocketException} indicating that a socket was closed or is not open for other reasons. - * + * * @author Christian Kohlschütter */ -public final class SocketClosedException extends SocketException { +public class SocketClosedException extends SocketException { private static final long serialVersionUID = 1L; /** @@ -36,7 +36,7 @@ public SocketClosedException() { /** * Constructs a new {@link SocketClosedException}. - * + * * @param msg The error message. */ public SocketClosedException(String msg) { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketImplShim.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketImplShim.java index c5829ad7b..0ea190ee6 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketImplShim.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketImplShim.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ /** * A shim that is filled with Java version-specific overrides. This variant is for Java 9 and above. - * + * * @author Christian Kohlschütter */ abstract class SocketImplShim extends SocketImpl { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketOptionsMapper.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketOptionsMapper.java index ebc58ac13..146c6091b 100644 --- a/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketOptionsMapper.java +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/SocketOptionsMapper.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ /** * Maps new SocketOption classes to the old integer-based scheme. - * + * * @author Christian Kohlschütter */ final class SocketOptionsMapper { diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/StackTraceUtil.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/StackTraceUtil.java new file mode 100644 index 000000000..472369c11 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/StackTraceUtil.java @@ -0,0 +1,49 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +/** + * Internal helper class to dump stack traces when deemed appropriate. + * + * NOTE: For junit testing classes, use {@code kohlschutter-test-util}'s {@code TestStackTraceUtil}. + * + * @author Christian Kohlschütter + */ +public final class StackTraceUtil { + private StackTraceUtil() { + throw new IllegalStateException("No instances"); + } + + /** + * Print/Log the stack trace of the given Throwable. + * + * @param t The throwable to log. + */ + public static void printStackTrace(Throwable t) { + t.printStackTrace(); // NOPMD + } + + /** + * Print/Log the stack trace of the given Throwable, marking this entry as a "severe condition". + * + * @param t The throwable to log. + */ + public static void printStackTraceSevere(Throwable t) { + t.printStackTrace(); // NOPMD + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/ThreadUtil.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/ThreadUtil.java new file mode 100644 index 000000000..6d5fef2fe --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/ThreadUtil.java @@ -0,0 +1,173 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.InterruptedIOException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.locks.LockSupport; + +import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; + +/** + * Helper class to support certain Thread-specific features. + * + * @author Christian Kohlschütter + */ +@IgnoreJRERequirement // see src/main/java20 +public final class ThreadUtil { + private static final ThreadLocal TREAT_AS_VIRTUAL_THREAD = new ThreadLocal<>(); + + private ThreadUtil() { + throw new IllegalStateException("No instances"); + } + + /** + * Checks if the current platform Thread is treated as a virtual one. + * + * @return {@code true} if so. + */ + private static boolean isTreatAsVirtualThread() { + return Boolean.TRUE.equals(TREAT_AS_VIRTUAL_THREAD.get()); + } + + /** + * Marks the current platform {@link Thread} to be treated as a virtual thread, if possible. Has + * no effect if the current Thread already is a virtual thread. + * + * @param b {@code true} to enable treatment of a platform thread as a virtual thread. + */ + public static void setTreatAsVirtualThread(boolean b) { + if (isVirtualThread()) { + return; + } + TREAT_AS_VIRTUAL_THREAD.set(b); + } + + /** + * Checks if the current {@link Thread} is to be considered a virtual thread. + * + * @return {@code true} if so. + */ + public static boolean isVirtualThread() { + return Thread.currentThread().isVirtual() || isTreatAsVirtualThread(); + } + + /** + * Checks if virtual threads are considered to be supported (and therefore if special support + * should be enabled). + * + * @return {@code true} if so. + */ + public static boolean isVirtualThreadSupported() { + return true; + } + + /** + * Returns a new "virtual thread per task executor". If virtual threads are not supported by this + * JVM, a new platform thread are created for each task, and such threads are marked to be treated + * as virtual threads. + * + * @return The new executor service. + */ + public static ExecutorService newVirtualThreadPerTaskExecutor() { + return Executors.newVirtualThreadPerTaskExecutor(); + } + + /** + * Checks if the current Thread has been interrupted, without clearing the flag; if interrupted, + * an {@link InterruptedIOException} is thrown, otherwise {@code true} is returned. + * + * @return {@code true}. + * @throws InterruptedIOException if interrupted. + */ + public static boolean checkNotInterruptedOrThrow() throws InterruptedIOException { + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedIOException(); + } + return true; + } + + /** + * Starts a new daemon thread. + * + * @param virtual If {@code true}, try to start a virtual Thread instead of a platform Thread (or + * at least pretend it's a virtual thread if they're not supported natively); if + * {@code false}, a "daemon" platform thread is started. + * @param run The runnable. + * @return The thread. + */ + public static Thread startNewDaemonThread(boolean virtual, Runnable run) { + if (virtual) { + return Thread.ofVirtual().start(run); + } else { + return Thread.ofPlatform().daemon(true).start(run); + } + } + + /** + * Ensures that the given operation is being executed on a system thread. If the current thread is + * a virtual thread, the operation is executed synchronously via + * {@link CompletableFuture#runAsync(Runnable)}: the virtual thread is suspended during that + * operation, and subsequently resumed. + * + * @param op The operation to run. + * @throws InterruptedException on interrupt. + */ + public static void runOnSystemThread(Runnable op) throws InterruptedException { + if (isVirtualThread()) { + Thread vt = Thread.currentThread(); + + CompletableFuture cf = CompletableFuture.runAsync(() -> { + try { + op.run(); + } finally { + LockSupport.unpark(vt); + } + }); + + LockSupport.park(); + try { + cf.get(); + } catch (ExecutionException e) { + Throwable t = e.getCause(); + if (t instanceof Error) { + throw (Error) t; // NOPMD.PreserveStackTrace + } else if (t instanceof RuntimeException) { + throw (RuntimeException) t; // NOPMD.PreserveStackTrace + } else { + throw new IllegalStateException(e); + } + } + } else { + boolean treatAsVirtual = isTreatAsVirtualThread(); + if (treatAsVirtual) { + setTreatAsVirtualThread(false); + } + try { + op.run(); + } finally { + if (treatAsVirtual) { + setTreatAsVirtualThread(treatAsVirtual); + } + } + } + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/UngrowableSet.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/UngrowableSet.java new file mode 100644 index 000000000..d0393e0d6 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/UngrowableSet.java @@ -0,0 +1,101 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +/** + * A {@link Set} that won't allow adding elements. + * + * @param The element type. + * @author Christian Kohlschütter + */ +final class UngrowableSet implements Set { + private final Set set; + + UngrowableSet(Set set) { + this.set = set; + } + + @Override + public int size() { + return set.size(); + } + + @Override + public boolean isEmpty() { + return set.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return set.contains(o); + } + + @Override + public Iterator iterator() { + return set.iterator(); + } + + @Override + public Object[] toArray() { + return set.toArray(); + } + + @Override + public E[] toArray(E[] a) { + return set.toArray(a); + } + + @Override + public boolean add(T e) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + return set.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return set.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection c) { + return set.retainAll(c); + } + + @Override + public boolean removeAll(Collection c) { + return set.removeAll(c); + } + + @Override + public void clear() { + set.clear(); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/Unsafe.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/Unsafe.java new file mode 100644 index 000000000..18eb9d2cb --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/Unsafe.java @@ -0,0 +1,41 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Declares some operation "unsafe", which is guarded via + * {@link AFSocketCapability#CAPABILITY_UNSAFE}. + *

+ * Methods annotated with this type should also call {@link AFSocket#ensureUnsafeSupported()} to + * check availability at runtime. + *

+ * NOTE: This annotation has nothing to do with {@code sun.misc.Unsafe}. + * + * @author Christian Kohlschütter + * @see AFSocket#ensureUnsafeSupported() + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Unsafe { + +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/VirtualThreadPoller.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/VirtualThreadPoller.java new file mode 100644 index 000000000..cdfc77b69 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/VirtualThreadPoller.java @@ -0,0 +1,54 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.Closeable; +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.nio.channels.SelectionKey; + +/** + * Helper class to support polling "virtually blocked" file descriptors for virtual threads. + * + * @author Christian Kohlschütter + */ +interface VirtualThreadPoller { + /** + * Returns the default instance best suited for the current system. + */ + VirtualThreadPoller INSTANCE = new VirtualThreadPollerNaive(); + + /** + * Parks the current thread until the given file descriptor is ready, with respect to the given + * readiness mode. + * + * @param fd The file descriptor to check. + * @param mode The mode (bitmask of {@link SelectionKey#OP_READ}, {@link SelectionKey#OP_WRITE}, + * {@link SelectionKey#OP_ACCEPT}, {@link SelectionKey#OP_CONNECT}) + * @param now The refence time (in millis) for the timeout + * @param timeout (in seconds), or 0 for infinite + * @param closeOnInterrupt Callback to call upon interrupt (usually to close the resource working + * on the file descriptor) + * @throws SocketTimeoutException on timeout + * @throws SocketClosedByInterruptException on interrupt. + * @throws IOException on other error + */ + void parkThreadUntilReady(FileDescriptor fd, /* SelectionKey.OP_ */ int mode, long now, + AFSupplier timeout, Closeable closeOnInterrupt) throws IOException; +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/VirtualThreadPollerNaive.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/VirtualThreadPollerNaive.java new file mode 100644 index 000000000..b070e841f --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/VirtualThreadPollerNaive.java @@ -0,0 +1,151 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.Closeable; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.SocketTimeoutException; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.locks.LockSupport; + +import org.eclipse.jdt.annotation.Nullable; +import org.newsclub.net.unix.AFSelector.PollFd; + +/** + * "Naive" implementation of {@link VirtualThreadPoller}, using + * {@link NativeUnixSocket#poll(PollFd, int)} on non-virtual threads. + * + * @author Christian Kohlschütter + */ +final class VirtualThreadPollerNaive implements VirtualThreadPoller { + private static final int POLL_INTERVAL_MILLIS = 1_000; // should remain at 1 second to simplify + // socket timeout handling + + private static final Map POLL_JOBS = new ConcurrentHashMap<>(); + + private static final InterruptedIOException POLL_INTERRUPTED_SENTINEL = + new InterruptedIOException(); + + private static final class PollJob { + private final List waitingThreads = new LinkedList<>(); + private final FileDescriptor fd; + private final int mode; + private final long now; + private final AFSupplier timeout; + + PollJob(FileDescriptor fd, int mode, long now, AFSupplier timeout) { + this.fd = fd; + this.mode = mode; + this.now = now; + this.timeout = timeout; + } + + @SuppressWarnings("PMD.CognitiveComplexity") + AFFuture<@Nullable IOException> trigger(Thread waitingThread) { + synchronized (fd) { + waitingThreads.add(waitingThread); + } + return AFFuture.supplyAsync(() -> { + try { + Thread thread = Thread.currentThread(); + PollFd pfd = new PollFd(new FileDescriptor[] {fd}, new int[] {mode}); + do { + if (thread.isInterrupted() || !fd.valid()) { + return POLL_INTERRUPTED_SENTINEL; + } + try { + NativeUnixSocket.poll(pfd, POLL_INTERVAL_MILLIS); + } catch (IOException e) { + return e; + } + if (thread.isInterrupted() || !fd.valid()) { + return POLL_INTERRUPTED_SENTINEL; + } + if (pfd.rops[0] != 0) { + break; + } + + int timeoutMillis = timeout.get(); + if (timeoutMillis > 0) { + if ((System.currentTimeMillis() - now) >= timeoutMillis) { + // handle in calling thread + break; + } + } + } while (true); // NOPMD.WhileLoopWithLiteralBoolean + } finally { + Thread threadToWake = null; + try { + synchronized (fd) { + threadToWake = waitingThreads.remove(0); + if (waitingThreads.isEmpty()) { + POLL_JOBS.remove(fd); + } + } + } finally { + if (threadToWake != null) { + LockSupport.unpark(threadToWake); + } + } + } + + return null; + })::get; + } + } + + @Override + public void parkThreadUntilReady(FileDescriptor fd, int mode, long now, + AFSupplier timeout, Closeable closeOnInterrupt) throws IOException { + Thread virtualThread = Thread.currentThread(); + + PollJob job = Java7Util.computeIfAbsent(POLL_JOBS, fd, (k) -> new PollJob(fd, mode, now, + timeout)); + AFFuture<@Nullable IOException> future = job.trigger(virtualThread); + + LockSupport.park(); + if (virtualThread.isInterrupted()) { + throw SocketClosedByInterruptException.newInstanceAndClose(closeOnInterrupt); + } + + try { + IOException ex = future.get(); + if (ex != null) { + if (ex == POLL_INTERRUPTED_SENTINEL) { + throw SocketClosedByInterruptException.newInstanceAndClose(closeOnInterrupt); + } + throw ex; + } + } catch (InterruptedException | ExecutionException e) { + throw SocketClosedByInterruptException.newInstanceAndClose(closeOnInterrupt); // NOPMD.PreserveStackTrace + } + + int timeoutMillis = timeout.get(); + if (timeoutMillis > 0) { + if ((System.currentTimeMillis() - now) >= timeoutMillis) { + throw new SocketTimeoutException(); + } + } + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/pool/ConcurrentQueueObjectPool.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/pool/ConcurrentQueueObjectPool.java new file mode 100644 index 000000000..035f33f45 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/pool/ConcurrentQueueObjectPool.java @@ -0,0 +1,105 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.pool; + +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +/** + * An {@link ObjectPool} implemented using {@link ConcurrentLinkedQueue}. + * + * @param The object type. + * @author Christian Kohlschütter + */ +public final class ConcurrentQueueObjectPool implements ObjectPool { + private final AtomicInteger count = new AtomicInteger(0); + private final Queue queue = new ConcurrentLinkedQueue<>(); + private final ObjectSupplier supplier; + private final int maxCapacity; + private final ObjectSanitizer sanitizer; + + /** + * Constructs a {@link ConcurrentQueueObjectPool} with the given capacity, supplier and sanitizer. + * + * @param supplier The supplier. + * @param sanitizer The sanitizer. + * @param maxCapacity The maximum capacity. + */ + public ConcurrentQueueObjectPool(ObjectSupplier<@NonNull O> supplier, + ObjectSanitizer<@NonNull O> sanitizer, final int maxCapacity) { + if (maxCapacity < 0) { + throw new IllegalArgumentException("maxCapacity"); + } + this.supplier = Objects.requireNonNull(supplier); + this.sanitizer = Objects.requireNonNull(sanitizer); + this.maxCapacity = maxCapacity; + } + + @Override + public Lease take() { + O obj = queue.poll(); + if (obj == null) { + obj = Objects.requireNonNull(supplier.get()); + } else { + count.decrementAndGet(); + } + return new LeaseImpl(obj); + } + + private final class LeaseImpl implements Lease { + private @Nullable O obj; + + public LeaseImpl(O obj) { + this.obj = obj; + } + + @SuppressWarnings("null") + @Override + public O get() { + return obj; + } + + @Override + public synchronized void close() { + @Nullable + O theObject = obj; + obj = null; + if (theObject != null) { + if (count.get() >= maxCapacity) { + // enough objects + } else if (!sanitizer.sanitize(theObject)) { + // decided not to reuse + } else { + if (queue.offer(theObject)) { + count.incrementAndGet(); + } + } + } + } + + @Override + public void discard() { + obj = null; + } + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/pool/MutableHolder.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/pool/MutableHolder.java new file mode 100644 index 000000000..5cd1064ac --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/pool/MutableHolder.java @@ -0,0 +1,57 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.pool; + +import org.eclipse.jdt.annotation.Nullable; + +/** + * Holds an object. + * + * @param The object type. + * @author Christian Kohlschütter + */ +public final class MutableHolder { + private @Nullable O obj; + + /** + * Creates a new {@link MutableHolder} object, holding the given object. + * + * @param obj The object to hold, or {@code null}. + */ + public MutableHolder(@Nullable O obj) { + this.obj = obj; + } + + /** + * Returns the current object. + * + * @return The object. + */ + public @Nullable O get() { + return obj; + } + + /** + * Sets the object. + * + * @param obj The object. + */ + public void set(@Nullable O obj) { + this.obj = obj; + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/pool/ObjectPool.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/pool/ObjectPool.java new file mode 100644 index 000000000..bbac77a58 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/pool/ObjectPool.java @@ -0,0 +1,145 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.pool; + +import java.io.Closeable; + +import org.eclipse.jdt.annotation.NonNull; +import org.newsclub.net.unix.ThreadUtil; + +/** + * A pool of objects. + * + * @param The object type. + * @author Christian Kohlschütter + */ +public interface ObjectPool { + + /** + * Creates a new {@link ObjectPool} that is used within a single thread; this may or may not be + * implemented using {@link ThreadLocal}, however the behavior should be comparable. + * + * @param The object type. + * @param supplier The object supplier. + * @param sanitizer The object sanitizer. + * @return The object pool. + */ + static ObjectPool newThreadLocalPool(ObjectSupplier<@NonNull O> supplier, + ObjectSanitizer<@NonNull O> sanitizer) { + if (ThreadUtil.isVirtualThreadSupported()) { + return new VirtualAwareThreadLocalObjectPool<>(supplier, sanitizer); + } else { + return new ThreadLocalObjectPool<>(supplier, sanitizer); + } + } + + /** + * Returns a {@link Lease} that is not backed by any object pool. + * + * @param The object type. + * @param obj The object. + * @return The lease; closing/discarding has no effect. + */ + static Lease unpooledLease(O obj) { + return new Lease() { + + @Override + public O get() { + return obj; + } + + @Override + public void close() { + } + + @Override + public void discard() { + } + }; + } + + /** + * Takes an exclusive lease of an object from the pool. If no existing object is available from + * the pool, a new one may be provided. + * + * @return The object. + */ + Lease take(); + + /** + * Supplies a leased object. + * + * @param The object type. + */ + @FunctionalInterface + interface ObjectSupplier { + + /** + * Gets a result. + * + * @return a result + */ + T get(); + } + + /** + * Sanitizes a previously leased object so it can be reused by the pool. + * + * @param The object type. + */ + @FunctionalInterface + interface ObjectSanitizer { + /** + * Sanitizes a previously leased object so it can be reused by the pool; if the object should + * not be reused, {@code false} is returned. + * + * @param obj The object to sanitize. + * @return {@code true} if sanitization was successful, {@code false} if the object should not + * be reused. + */ + boolean sanitize(T obj); + } + + /** + * A lease for an object (obtained via {@link #get()}); working with the object is only permitted + * before {@link #close()}. + * + * @param The object type. + */ + interface Lease extends Closeable { + /** + * Returns the leased object, potentially {@code null} when discarded/closed. + * + * @return The object, or {@code null}. + */ + O get(); + + /** + * Terminates the validity of this lease. Unless discarded via {@link #discard()}, the object + * may end up back in the object pool it was leased from; however that is decided by the pool. + */ + @Override + void close(); + + /** + * Marks the leased object as discarded, potentially preventing it from being reused in the + * object pool. + */ + void discard(); + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/pool/ThreadLocalObjectPool.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/pool/ThreadLocalObjectPool.java new file mode 100644 index 000000000..de07e55d2 --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/pool/ThreadLocalObjectPool.java @@ -0,0 +1,64 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.pool; + +import org.eclipse.jdt.annotation.NonNull; + +final class ThreadLocalObjectPool implements ObjectPool { + private final ThreadLocal tl; + private final ObjectSanitizer sanitizer; + + private final Lease leaseImpl = new Lease() { + private boolean discarded = false; + + @Override + public void close() { + if (!discarded && !sanitizer.sanitize(tl.get())) { + tl.remove(); + } + } + + @Override + public O get() { + return tl.get(); + } + + @Override + public void discard() { + discarded = true; + tl.remove(); + } + }; + + ThreadLocalObjectPool(ObjectSupplier<@NonNull O> supplier, + ObjectSanitizer<@NonNull O> sanitizer) { + this.sanitizer = sanitizer; + tl = new ThreadLocal() { + + @Override + protected O initialValue() { + return supplier.get(); + } + }; + } + + @Override + public Lease take() { + return leaseImpl; + } +} diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/pool/VirtualAwareThreadLocalObjectPool.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/pool/VirtualAwareThreadLocalObjectPool.java new file mode 100644 index 000000000..abfc91a0e --- /dev/null +++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/pool/VirtualAwareThreadLocalObjectPool.java @@ -0,0 +1,42 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.pool; + +import org.eclipse.jdt.annotation.NonNull; +import org.newsclub.net.unix.ThreadUtil; + +final class VirtualAwareThreadLocalObjectPool implements ObjectPool { + private final ThreadLocalObjectPool tlPool; + private final ConcurrentQueueObjectPool cqPool; + + public VirtualAwareThreadLocalObjectPool(ObjectSupplier<@NonNull O> supplier, + ObjectSanitizer<@NonNull O> sanitizer) { + this.tlPool = new ThreadLocalObjectPool<>(supplier, sanitizer); + this.cqPool = new ConcurrentQueueObjectPool<>(supplier, sanitizer, Runtime.getRuntime() + .availableProcessors() * 2); + } + + @Override + public Lease take() { + if (ThreadUtil.isVirtualThread()) { + return cqPool.take(); + } else { + return tlPool.take(); + } + } +} diff --git a/junixsocket-common/src/main/java14/org/newsclub/net/unix/SelectorProviderShim.java b/junixsocket-common/src/main/java14/org/newsclub/net/unix/SelectorProviderShim.java new file mode 100644 index 000000000..aa11c912f --- /dev/null +++ b/junixsocket-common/src/main/java14/org/newsclub/net/unix/SelectorProviderShim.java @@ -0,0 +1,37 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.IOException; +import java.net.ProtocolFamily; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.channels.spi.SelectorProvider; +import java.util.Objects; + +abstract class SelectorProviderShim extends SelectorProvider { + public SocketChannel openSocketChannel(ProtocolFamily family) throws IOException { + Objects.requireNonNull(family); + throw new UnsupportedOperationException("Protocol family not supported"); + } + + public ServerSocketChannel openServerSocketChannel(ProtocolFamily family) throws IOException { + Objects.requireNonNull(family); + throw new UnsupportedOperationException("Protocol family not supported"); + } +} diff --git a/junixsocket-common/src/main/java15/org/newsclub/net/unix/SocketAddressUtil.java b/junixsocket-common/src/main/java15/org/newsclub/net/unix/SocketAddressUtil.java new file mode 100644 index 000000000..5f8ee3883 --- /dev/null +++ b/junixsocket-common/src/main/java15/org/newsclub/net/unix/SocketAddressUtil.java @@ -0,0 +1,52 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.net.SocketAddress; +import java.net.SocketException; + +/** + * {@link SocketAddress}-related helper methods. + * + * @author Christian Kohlschütter + */ +final class SocketAddressUtil { + private SocketAddressUtil() { + throw new IllegalStateException("No instances"); + } + + /** + * Try to convert a {@link SocketAddress} that is not a {@link AFSocketAddress} to one that is. + * + * @param address The address. + * @return A supplier for the given address, or {@code null}. + */ + static AFSupplier supplyAFSocketAddress(SocketAddress address) { + return supplyAFUNIXSocketAddress(address); + } + + /** + * Try to convert a {@link SocketAddress} that is not a {@link AFSocketAddress} to one that is. + * + * @param address The address. + * @return A supplier for the given address, or {@code null}. + */ + static AFSupplier supplyAFUNIXSocketAddress(SocketAddress address) { + return null; + } +} diff --git a/junixsocket-common/src/main/java16/multirelease.md b/junixsocket-common/src/main/java16/multirelease.md new file mode 100644 index 000000000..d6f0b3dc5 --- /dev/null +++ b/junixsocket-common/src/main/java16/multirelease.md @@ -0,0 +1 @@ +Marker file to ensure we generate code under META-INF/versions/16 diff --git a/junixsocket-common/src/main/java20/org/newsclub/net/unix/ThreadUtil.java b/junixsocket-common/src/main/java20/org/newsclub/net/unix/ThreadUtil.java new file mode 100644 index 000000000..aa9dd32bb --- /dev/null +++ b/junixsocket-common/src/main/java20/org/newsclub/net/unix/ThreadUtil.java @@ -0,0 +1,157 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.InterruptedIOException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; + +import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; + +/** + * Helper class to support certain Thread-specific features. + * + * @author Christian Kohlschütter + */ +@IgnoreJRERequirement // see src/main/java20 +public final class ThreadUtil { + private static final ThreadLocal TREAT_AS_VIRTUAL_THREAD = new ThreadLocal<>(); + + private ThreadUtil() { + throw new IllegalStateException("No instances"); + } + + /** + * Checks if the current platform Thread is treated as a virtual one. + * + * @return {@code true} if so. + */ + private static boolean isTreatAsVirtualThread() { + return Boolean.TRUE.equals(TREAT_AS_VIRTUAL_THREAD.get()); + } + + /** + * Marks the current platform {@link Thread} to be treated as a virtual thread, if possible. Has + * no effect if the current Thread already is a virtual thread. + * + * @param b {@code true} to enable treatment of a platform thread as a virtual thread. + */ + public static void setTreatAsVirtualThread(boolean b) { + if (isVirtualThread()) { + return; + } + TREAT_AS_VIRTUAL_THREAD.set(b); + } + + /** + * Checks if the current {@link Thread} is to be considered a virtual thread. + * + * @return {@code true} if so. + */ + public static boolean isVirtualThread() { + return isTreatAsVirtualThread(); + } + + /** + * Checks if virtual threads are considered to be supported (and therefore if special support + * should be enabled). + * + * @return {@code true} if so. + */ + public static boolean isVirtualThreadSupported() { + return false; + } + + /** + * Returns a new "virtual thread per task executor". If virtual threads are not supported by this + * JVM, a new platform thread are created for each task, and such threads are marked to be treated + * as virtual threads. + * + * @return The new executor service. + */ + public static ExecutorService newVirtualThreadPerTaskExecutor() { + return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0L, TimeUnit.SECONDS, + new SynchronousQueue(), (r) -> startNewDaemonThread(true, r)); + } + + /** + * Checks if the current Thread has been interrupted, without clearing the flag; if interrupted, + * an {@link InterruptedIOException} is thrown, otherwise {@code true} is returned. + * + * @return {@code true}. + * @throws InterruptedIOException if interrupted. + */ + public static boolean checkNotInterruptedOrThrow() throws InterruptedIOException { + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedIOException(); + } + return true; + } + + /** + * Starts a new daemon thread. + * + * @param virtual If {@code true}, try to start a virtual Thread instead of a platform Thread (or + * at least pretend it's a virtual thread if they're not supported natively); if + * {@code false}, a "daemon" platform thread is started. + * @param run The runnable. + * @return The thread. + */ + public static Thread startNewDaemonThread(boolean virtual, Runnable run) { + if (virtual) { + final Runnable run0 = run; + run = () -> { + setTreatAsVirtualThread(true); + run0.run(); + }; + } + Thread t = new Thread(run); + t.setDaemon(true); + t.start(); + return t; + } + + /** + * Ensures that the given operation is being executed on a system thread. If the current thread is + * a virtual thread, the operation is executed synchronously via + * {@link CompletableFuture#runAsync(Runnable)}: the virtual thread is suspended during that + * operation, and subsequently resumed. + * + * @param op The operation to run. + * @throws InterruptedException on interrupt. + */ + public static void runOnSystemThread(Runnable op) throws InterruptedException { + boolean treatAsVirtual = isTreatAsVirtualThread(); + if (treatAsVirtual) { + setTreatAsVirtualThread(false); + } + try { + op.run(); + } finally { + if (treatAsVirtual) { + setTreatAsVirtualThread(treatAsVirtual); + } + } + } +} diff --git a/junixsocket-common/src/main/java7/multirelease.md b/junixsocket-common/src/main/java7/multirelease.md new file mode 100644 index 000000000..713e9e182 --- /dev/null +++ b/junixsocket-common/src/main/java7/multirelease.md @@ -0,0 +1,3 @@ +This repository is kept compatible with Java 7. + +Do not remove this file, it is used as a marker by the Maven build scripts. diff --git a/junixsocket-common/src/main/java8/org/newsclub/net/unix/AFInputStream.java b/junixsocket-common/src/main/java8/org/newsclub/net/unix/AFInputStream.java index bd11450b5..11fb58ed2 100644 --- a/junixsocket-common/src/main/java8/org/newsclub/net/unix/AFInputStream.java +++ b/junixsocket-common/src/main/java8/org/newsclub/net/unix/AFInputStream.java @@ -1,3 +1,20 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.newsclub.net.unix; import java.io.IOException; diff --git a/junixsocket-common/src/main/java8/org/newsclub/net/unix/AFOutputStream.java b/junixsocket-common/src/main/java8/org/newsclub/net/unix/AFOutputStream.java index 4523aa88f..632324380 100644 --- a/junixsocket-common/src/main/java8/org/newsclub/net/unix/AFOutputStream.java +++ b/junixsocket-common/src/main/java8/org/newsclub/net/unix/AFOutputStream.java @@ -1,3 +1,20 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.newsclub.net.unix; import java.io.IOException; @@ -16,11 +33,11 @@ public abstract class AFOutputStream extends OutputStream implements FileDescrip * Reads all bytes from the given input stream and writes the bytes to this output stream in the * order that they are read. On return, this input stream will be at end of stream. This method * does not close either stream. - * + * * This method effectively is the reverse notation of * {@link InputStream#transferTo(OutputStream)}, which may or may not be optimized for * {@link AFSocket}s. - * + * * @param in The {@link InputStream} to transfer from. * @return The number of bytes transferred. * @throws IOException on error. diff --git a/junixsocket-common/src/main/java8/org/newsclub/net/unix/CleanableState.java b/junixsocket-common/src/main/java8/org/newsclub/net/unix/CleanableState.java index d78704771..b09b42338 100644 --- a/junixsocket-common/src/main/java8/org/newsclub/net/unix/CleanableState.java +++ b/junixsocket-common/src/main/java8/org/newsclub/net/unix/CleanableState.java @@ -1,3 +1,20 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.newsclub.net.unix; import java.io.Closeable; @@ -18,7 +35,7 @@ public final void runCleaner() { } @SuppressWarnings("all") - @Override + @Deprecated protected final void finalize() { try { runCleaner(); diff --git a/junixsocket-common/src/main/java8/org/newsclub/net/unix/DatagramSocketImplShim.java b/junixsocket-common/src/main/java8/org/newsclub/net/unix/DatagramSocketImplShim.java index 3bdb3ac43..759909bfe 100644 --- a/junixsocket-common/src/main/java8/org/newsclub/net/unix/DatagramSocketImplShim.java +++ b/junixsocket-common/src/main/java8/org/newsclub/net/unix/DatagramSocketImplShim.java @@ -1,10 +1,27 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.newsclub.net.unix; import java.net.DatagramSocketImpl; /** * A shim that is filled with Java version-specific overrides. This variant is for Java 7 and 8. - * + * * @author Christian Kohlschütter */ abstract class DatagramSocketImplShim extends DatagramSocketImpl { @@ -13,7 +30,7 @@ protected DatagramSocketImplShim() { } @SuppressWarnings("all") - @Override + @Deprecated protected final void finalize() { try { close(); diff --git a/junixsocket-common/src/main/java8/org/newsclub/net/unix/DatagramSocketShim.java b/junixsocket-common/src/main/java8/org/newsclub/net/unix/DatagramSocketShim.java new file mode 100644 index 000000000..a4bd85316 --- /dev/null +++ b/junixsocket-common/src/main/java8/org/newsclub/net/unix/DatagramSocketShim.java @@ -0,0 +1,51 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.DatagramSocketImpl; + +abstract class DatagramSocketShim extends DatagramSocket { + + protected DatagramSocketShim(DatagramSocketImpl impl) { + super(impl); + } + + /** + * Returns the value of a junixsocket socket option. + * + * @param The type of the socket option value. + * @param name The socket option. + * @return The value of the socket option. + * @throws IOException on error. + */ + public abstract T getOption(AFSocketOption name) throws IOException; + + /** + * Sets the value of a socket option. + * + * @param The type of the socket option value. + * @param name The socket option. + * @param value The value of the socket option. + * @return this DatagramSocket. + * @throws IOException on error. + */ + @SuppressWarnings("PMD.LinguisticNaming") + public abstract DatagramSocket setOption(AFSocketOption name, T value) throws IOException; +} diff --git a/junixsocket-common/src/main/java8/org/newsclub/net/unix/Java7Util.java b/junixsocket-common/src/main/java8/org/newsclub/net/unix/Java7Util.java new file mode 100644 index 000000000..9bcb999eb --- /dev/null +++ b/junixsocket-common/src/main/java8/org/newsclub/net/unix/Java7Util.java @@ -0,0 +1,99 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Semaphore; + +import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; +import org.eclipse.jdt.annotation.Nullable; + +/** + * Helper class to allow codebase to remain compatible with Java 7 (via retrolambda, + * animal-sniffer). + * + * @author Christian Kohlschütter + */ +@IgnoreJRERequirement // see src/main/java8 +final class Java7Util { + private static final boolean JAVA8_OR_LATER; + + static { + boolean ok; + try { + Class.forName("java.util.concurrent.CompletableFuture"); + ok = true; + } catch (ClassNotFoundException e) { + ok = false; + } + JAVA8_OR_LATER = ok; + } + + static <@Nullable U> AFFuture supplyAsync(AFSupplier supplier) { + if (JAVA8_OR_LATER) { + return CompletableFuture.supplyAsync(supplier::get)::get; + } else { + return new Java7CompletableFuture<>(supplier); + } + } + + static V computeIfAbsent(Map map, K key, + AFFunction mappingFunction) { + if (JAVA8_OR_LATER) { + return map.computeIfAbsent(key, mappingFunction::apply); + } + + Objects.requireNonNull(mappingFunction); + V v; + if ((v = map.get(key)) == null) { + V newValue; + if ((newValue = mappingFunction.apply(key)) != null) { + map.put(key, newValue); + return newValue; + } + } + + return v; + } + + static final class Java7CompletableFuture<@Nullable T> implements AFFuture { + private final Semaphore sema = new Semaphore(0); + private T object = null; + + Java7CompletableFuture(AFSupplier supplier) { + Thread t = new Thread(new Runnable() { + + @Override + public void run() { + object = supplier.get(); + sema.release(); + } + }); + t.start(); + } + + @Override + public T get() throws InterruptedException, ExecutionException { + sema.acquire(); + return object; + } + } +} diff --git a/junixsocket-common/src/main/java8/org/newsclub/net/unix/PathUtil.java b/junixsocket-common/src/main/java8/org/newsclub/net/unix/PathUtil.java new file mode 100644 index 000000000..fa09feaa5 --- /dev/null +++ b/junixsocket-common/src/main/java8/org/newsclub/net/unix/PathUtil.java @@ -0,0 +1,48 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Path; + +/** + * Some {@link Path}-related helper methods. + * + * @author Christian Kohlschütter + */ +final class PathUtil { + private PathUtil() { + throw new IllegalStateException("No instances"); + } + + /** + * Checks if the given path is in the default file system. + * + * @param p The path. + * @return {@code true} if so. + */ + @SuppressWarnings("PMD.CompareObjectsWithEquals") + static boolean isPathInDefaultFileSystem(Path p) { + FileSystem fs = p.getFileSystem(); + if (fs != FileSystems.getDefault()) { + return false; + } + return true; + } +} diff --git a/junixsocket-common/src/main/java8/org/newsclub/net/unix/SocketImplShim.java b/junixsocket-common/src/main/java8/org/newsclub/net/unix/SocketImplShim.java index d5e3d8963..0321018e9 100644 --- a/junixsocket-common/src/main/java8/org/newsclub/net/unix/SocketImplShim.java +++ b/junixsocket-common/src/main/java8/org/newsclub/net/unix/SocketImplShim.java @@ -1,3 +1,20 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.newsclub.net.unix; import java.io.IOException; @@ -8,7 +25,7 @@ /** * A shim that is filled with Java version-specific overrides. This variant is for Java 7 and 8. - * + * * @author Christian Kohlschütter */ abstract class SocketImplShim extends SocketImpl { @@ -17,7 +34,7 @@ protected SocketImplShim() { } @SuppressWarnings("all") - @Override + @Deprecated protected final void finalize() { try { close(); diff --git a/junixsocket-common/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-common/jni-config.json b/junixsocket-common/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-common/jni-config.json index ca3ffe2b7..d54b568d0 100644 --- a/junixsocket-common/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-common/jni-config.json +++ b/junixsocket-common/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-common/jni-config.json @@ -8,6 +8,10 @@ "name":"java.lang.Boolean", "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }] }, +{ + "name":"java.lang.Class", + "methods":[{"name":"getName","parameterTypes":[] }] +}, { "name":"java.lang.IllegalStateException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] @@ -27,6 +31,10 @@ "name":"java.lang.NullPointerException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] }, +{ + "name":"java.lang.Object", + "methods":[{"name":"getClass","parameterTypes":[] }] +}, { "name":"java.lang.ProcessBuilder$RedirectPipeImpl", "fields":[{"name":"fd"}], @@ -51,7 +59,8 @@ }, { "name":"java.net.InetSocketAddress", - "fields":[{"name":"holder"}] + "fields":[{"name":"holder"}], + "methods":[{"name":"","parameterTypes":["int"] }] }, { "name":"java.net.InetSocketAddress$InetSocketAddressHolder", @@ -84,6 +93,9 @@ "name":"java.nio.channels.spi.AbstractSelectableChannel", "methods":[{"name":"removeKey","parameterTypes":["java.nio.channels.SelectionKey"] }] }, +{ + "name":"org.newsclub.net.unix.AFGenericServerSocket" +}, { "name":"org.newsclub.net.unix.AFSelector$PollFd", "fields":[ @@ -132,18 +144,44 @@ {"name":"setTipcErrorInfo","parameterTypes":["int","int"] } ] }, +{ + "name":"org.newsclub.net.unix.BrokenPipeSocketException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.newsclub.net.unix.ConnectionResetSocketException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, { "name":"org.newsclub.net.unix.InvalidArgumentSocketException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] }, +{ + "name":"org.newsclub.net.unix.NoSuchDeviceSocketException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.newsclub.net.unix.NotConnectedSocketException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.newsclub.net.unix.AddressUnavailableSocketException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, { "name":"org.newsclub.net.unix.OperationNotSupportedSocketException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] }, { + "name":"org.newsclub.net.unix.SocketClosedException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "condition" : { "typeReachable" : "org.newsclub.net.unix.tipc.AFTIPCDatagramSocket" }, "name":"org.newsclub.net.unix.tipc.AFTIPCDatagramSocket" }, { + "condition" : { "typeReachable" : "org.newsclub.net.unix.tipc.AFTIPCGroupRequest" }, "name":"org.newsclub.net.unix.tipc.AFTIPCGroupRequest", "methods":[ {"name":"fromNative","parameterTypes":["int","int","int","int"] }, @@ -154,15 +192,50 @@ ] }, { + "condition" : { "typeReachable" : "org.newsclub.net.unix.tipc.AFTIPCSocket" }, "name":"org.newsclub.net.unix.tipc.AFTIPCSocket" }, { + "condition" : { "typeReachable" : "org.newsclub.net.unix.tipc.AFVSOCKDatagramSocket" }, "name":"org.newsclub.net.unix.vsock.AFVSOCKDatagramSocket" }, { + "condition" : { "typeReachable" : "org.newsclub.net.unix.tipc.AFVSOCKSocket" }, "name":"org.newsclub.net.unix.vsock.AFVSOCKSocket" }, { + "condition" : { "typeReachable" : "org.newsclub.net.unix.tipc.AFVSOCKServerSocket" }, "name":"org.newsclub.net.unix.vsock.AFVSOCKServerSocket" +}, +{ + "name":"java.net.SocketImpl" +}, +{ + "name":"java.net.DatagramSocketImpl" +}, +{ + "name":"org.newsclub.net.unix.AFSocketImpl" +}, +{ + "name":"org.newsclub.net.unix.SocketImplShim" +}, +{ + "name":"org.newsclub.net.unix.AFUNIXSocketImpl" +}, +{ + "name":"org.newsclub.net.unix.AFUNIXDatagramSocketImpl" +}, +{ + "name":"org.newsclub.net.unix.AFDatagramSocketImpl" +}, +{ + "name":"org.newsclub.net.unix.darwin.system.AFSYSTEMDatagramSocket" +}, +{ + "name":"org.newsclub.net.unix.darwin.system.AFSYSTEMSocket" +}, +{ + "name":"java.lang.ThreadLocal", + "methods":[{"name":"get","parameterTypes":[] }] } -] \ No newline at end of file +] diff --git a/junixsocket-common/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-common/reflect-config.json b/junixsocket-common/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-common/reflect-config.json index 7c4e0ed58..2167c098c 100644 --- a/junixsocket-common/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-common/reflect-config.json +++ b/junixsocket-common/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-common/reflect-config.json @@ -3,13 +3,16 @@ "name":"java.nio.channels.spi.AbstractSelectableChannel" }, { + "condition" : { "typeReachable" : "org.newsclub.lib.junixsocket.common.NarMetadata" }, "name":"org.newsclub.lib.junixsocket.common.NarMetadata" }, { + "condition" : { "typeReachable" : "org.newsclub.lib.junixsocket.custom.NarMetadata" }, "name":"org.newsclub.lib.junixsocket.custom.NarMetadata" }, { - "name":"org.newsclub.net.unix.AFSocketCapabilityRequirement" + "name":"org.newsclub.net.unix.AFGenericSocketAddress", + "methods":[{"name":"addressFamily","parameterTypes":[] }] }, { "name":"org.newsclub.net.unix.AFTIPCSocketAddress", @@ -24,9 +27,140 @@ "methods":[{"name":"addressFamily","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.tipc.AFTIPCSelectorProvider" + "name":"org.newsclub.net.unix.AFUNIXSelectorProvider", + "allPublicMethods":true +}, +{ + "condition" : { "typeReachable" : "org.newsclub.net.unix.tipc.AFTIPCSelectorProvider" }, + "name":"org.newsclub.net.unix.tipc.AFTIPCSelectorProvider", + "allPublicMethods":true +}, +{ + "condition" : { "typeReachable" : "org.newsclub.net.unix.vsock.AFVSOCKSelectorProvider" }, + "name":"org.newsclub.net.unix.vsock.AFVSOCKSelectorProvider", + "allPublicMethods":true +}, +{ + "condition" : { "typeReachable" : "org.newsclub.net.unix.darwin.system.AFSYSTEMSelectorProvider" }, + "name":"org.newsclub.net.unix.darwin.system.AFSYSTEMSelectorProvider", + "allPublicMethods":true +}, +{ + "name":"com.kohlschutter.testutil.AvailabilityExecutionCondition", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.kohlschutter.testutil.AvailabilityRequirement", + "queryAllPublicMethods":true +}, +{ + "name":"com.kohlschutter.testutil.ExecutionEnvironmentExecutionCondition", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.kohlschutter.testutil.ExecutionEnvironmentRequirement", + "queryAllPublicMethods":true +}, +{ + "name":"java.net.InetSocketAddress" +}, +{ + "name":"java.net.UnixDomainSocketAddress", + "methods":[{"name":"of","parameterTypes":["java.nio.file.Path"] }] +}, +{ + "name":"java.nio.channels.DatagramChannel", + "methods":[{"name":"open","parameterTypes":["java.net.ProtocolFamily"] }] +}, +{ + "name":"java.nio.channels.ServerSocketChannel", + "methods":[{"name":"open","parameterTypes":["java.net.ProtocolFamily"] }] +}, +{ + "name":"java.nio.channels.SocketChannel", + "methods":[{"name":"open","parameterTypes":["java.net.ProtocolFamily"] }] +}, +{ + "name":"org.newsclub.net.unix.AFSYSTEMSocketAddress", + "methods":[{"name":"addressFamily","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.OperationNotSupportedSocketException" +}, +{ + "name":"org.newsclub.net.unix.InvalidArgumentSocketException" +}, +{ + "name":"org.newsclub.net.unix.NoSuchDeviceSocketException" +}, +{ + "name":"org.newsclub.net.unix.AddressUnavailableSocketException" +}, +{ + "name":"org.newsclub.net.unix.BrokenPipeSocketException" +}, +{ + "name":"org.newsclub.net.unix.ConnectionResetSocketException" +}, +{ + "name":"java.util.function.Consumer", + "queryAllPublicMethods":true +}, +{ + "name":"[J" +}, +{ + "name":"org.newsclub.net.unix.SocketClosedException" +}, +{ + "name":"[Ljava.lang.StackTraceElement;" +}, +{ + "name":"java.io.IOException" +}, +{ + "name":"java.lang.Exception" +}, +{ + "name":"java.lang.Number" +}, +{ + "name":"java.lang.Throwable" +}, +{ + "name":"java.lang.reflect.Proxy" +}, +{ + "name":"java.util.Collections$EmptyList" +}, +{ + "name":"java.util.UUID" +}, +{ + "name":"org.newsclub.net.unix.generic.AFGenericSelectorProvider" +}, +{ + "name":"java.util.concurrent.atomic.AtomicInteger" +}, +{ + "name":"org.newsclub.net.unix.AFUNIXSocketCredentials" +}, +{ + "name":"org.newsclub.net.unix.AFUNIXSocketFactory" +}, +{ + "name":"org.newsclub.net.unix.AFUNIXSocketFactory$FactoryArg", + "allPublicConstructors":true, + "allPublicMethods":true +}, +{ + "name":"org.newsclub.net.unix.AFUNIXSocketFactory$SystemProperty", + "allPublicConstructors":true, + "allPublicMethods":true }, { - "name":"org.newsclub.net.unix.vsock.AFVSOCKSelectorProvider" + "name":"org.newsclub.net.unix.AFUNIXSocketFactory$URIScheme", + "allPublicConstructors":true, + "allPublicMethods":true } ] diff --git a/junixsocket-common/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-common/resource-config.json b/junixsocket-common/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-common/resource-config.json index d75817c78..66bf7d3f3 100644 --- a/junixsocket-common/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-common/resource-config.json +++ b/junixsocket-common/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-common/resource-config.json @@ -5,10 +5,27 @@ "pattern":"\\QMETA-INF/maven/com.kohlschutter.junixsocket/junixsocket-common/pom.properties\\E" }, { + "condition" : { "typeReachable" : "org.newsclub.lib.junixsocket.common.NarMetadata" }, "pattern":"\\QMETA-INF/maven/com.kohlschutter.junixsocket/junixsocket-native-common/pom.properties\\E" }, { + "condition" : { "typeReachable" : "org.newsclub.lib.junixsocket.custom.NarMetadata" }, "pattern":"\\QMETA-INF/maven/com.kohlschutter.junixsocket/junixsocket-native-custom/pom.properties\\E" + }, + { + "pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, + { + "pattern":"\\QMETA-INF/services/java.net.spi.InetAddressResolverProvider\\E" + }, + { + "pattern":"\\QMETA-INF/services/java.net.spi.URLStreamHandlerProvider\\E" + }, + { + "pattern":"\\QMETA-INF/services/java.nio.channels.spi.SelectorProvider\\E" + }, + { + "pattern":"\\QMETA-INF/services/java.util.spi.ResourceBundleControlProvider\\E" } ]}, "bundles":[] diff --git a/junixsocket-common/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-common/serialization-config.json b/junixsocket-common/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-common/serialization-config.json index c6a8c2121..713f680be 100644 --- a/junixsocket-common/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-common/serialization-config.json +++ b/junixsocket-common/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-common/serialization-config.json @@ -1,9 +1,71 @@ { "types":[ { - "name":"java.net.InetAddress" + "name": "byte[]" + }, + { + "name": "java.lang.String" + }, + { + "name": "java.io.IOException" + }, + { + "name": "java.lang.Exception" + }, + { + "name": "java.lang.Number" + }, + { + "name": "java.lang.StackTraceElement[]" + }, + { + "name": "java.lang.Throwable" + }, + { + "name": "java.lang.reflect.Proxy" + }, + { + "name": "java.net.InetAddress" + }, + { + "name": "java.net.InetSocketAddress" + }, + { + "name": "java.net.SocketAddress" + }, + { + "name": "org.newsclub.net.unix.AFSocketAddress" + }, + { + "name": "java.util.Collections$EmptyList" + }, + { + "name": "java.util.UUID" + }, + { + "name": "java.util.concurrent.atomic.AtomicInteger" + }, + { + "name": "long[]" + }, + { + "name": "org.newsclub.net.unix.AFUNIXSocketAddress" + }, + { + "name": "org.newsclub.net.unix.AFTIPCSocketAddress" + }, + { + "name": "org.newsclub.net.unix.AFVSOCKSocketAddress" + }, + { + "name": "org.newsclub.net.unix.AFSYSTEMSocketAddress" + }, + { + "name": "org.newsclub.net.unix.AFUNIXSocketCredentials" } ], "lambdaCapturingTypes":[ + ], + "proxies":[ ] } \ No newline at end of file diff --git a/junixsocket-common/src/site/site.xml b/junixsocket-common/src/site/site.xml index 873972284..6d13afab6 100644 --- a/junixsocket-common/src/site/site.xml +++ b/junixsocket-common/src/site/site.xml @@ -7,6 +7,7 @@ org.apache.maven.skins maven-fluido-skin + 2.0.0-M8 diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/AFDatagramUtil.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/AFDatagramUtil.java index db1cf6755..64c85eaf9 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/AFDatagramUtil.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/AFDatagramUtil.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,11 +23,11 @@ /** * Helper methods for datagrams, but mostly for testing. - * + * * @author Christian Kohlschütter */ public final class AFDatagramUtil { - @ExcludeFromCodeCoverageGeneratedReport + @ExcludeFromCodeCoverageGeneratedReport(reason = "unreachable") private AFDatagramUtil() { throw new IllegalStateException("No instances"); } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/AFSYSTEMSocketAddressTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/AFSYSTEMSocketAddressTest.java new file mode 100644 index 000000000..0782eefba --- /dev/null +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/AFSYSTEMSocketAddressTest.java @@ -0,0 +1,82 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.net.SocketException; +import java.net.URI; +import java.util.Arrays; + +import org.junit.jupiter.api.Test; +import org.newsclub.net.unix.AFSYSTEMSocketAddress.SysAddr; + +import com.kohlschutter.testutil.AssertUtil; + +public class AFSYSTEMSocketAddressTest { + + @Test + public void testSchemesAvailable() throws Exception { + AssertUtil.assertSetContains(AFAddressFamily.uriSchemes(), // + Arrays.asList("afsystem")); + } + + @Test + public void testParseFail() throws Exception { + assertThrows(SocketException.class, () -> AFSYSTEMSocketAddress.of(URI.create( + "afsystem://invalid"))); + assertThrows(SocketException.class, () -> AFSYSTEMSocketAddress.of(URI.create( + "afsystem://socket.23/"))); + } + + private AFSYSTEMSocketAddress validateAddress(String uri) throws IOException { + URI u = URI.create(uri); + AFSYSTEMSocketAddress address = AFSYSTEMSocketAddress.of(u); + URI u2 = address.toURI(u.getScheme(), null); + assertEquals(address, AFSYSTEMSocketAddress.of(u2)); + return address; + } + + @Test + public void testURI() throws Exception { + assertEquals(AFSYSTEMSocketAddress.ofSysAddrIdUnit(SysAddr.AF_SYS_CONTROL, 3, 4), + validateAddress("afsystem://2.3.4")); + } + + @Test + public void testSocatString() throws Exception { + String socatString; + try { + socatString = AFSYSTEMSocketAddress.ofSysAddrIdUnit(SysAddr.AF_SYS_CONTROL, 3, 4) + .toSocatAddressString(AFSocketType.SOCK_STREAM, AFSocketProtocol.DEFAULT); + assertNotNull(socatString); + } catch (SocketException e) { + if (AFSocket.supports(AFSocketCapability.CAPABILITY_DARWIN)) { + throw e; + } else { + // expected + return; + } + } + assertTrue(socatString.contains(":")); + } +} diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/AFSocketCapabilityCondition.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/AFSocketCapabilityCondition.java index 987f587c5..15c03e3c1 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/AFSocketCapabilityCondition.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/AFSocketCapabilityCondition.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,12 @@ import org.junit.jupiter.api.extension.ExtensionContext; public final class AFSocketCapabilityCondition implements ExecutionCondition { + /** + * Constructs a new {@link AFSocketCapabilityCondition}. + */ + public AFSocketCapabilityCondition() { + } + @SuppressWarnings("exports") @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/AFSocketCapabilityRequirement.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/AFSocketCapabilityRequirement.java index c6c37d7d7..b01b52cbe 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/AFSocketCapabilityRequirement.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/AFSocketCapabilityRequirement.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/AFTIPCSocketAddressTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/AFTIPCSocketAddressTest.java index ec22687b4..61bd235d7 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/AFTIPCSocketAddressTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/AFTIPCSocketAddressTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ package org.newsclub.net.unix; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -125,12 +125,19 @@ public void testGeneric() throws Exception { @Test public void testSocatString() throws Exception { - String socatString = AFTIPCSocketAddress.ofService(123, 456).toSocatAddressString( - AFSocketType.SOCK_STREAM, AFSocketProtocol.DEFAULT); - if (socatString == null) { - assertFalse(AFSocket.supports(AFSocketCapability.CAPABILITY_TIPC)); - } else { - assertTrue(socatString.contains(":")); + String socatString; + try { + socatString = AFTIPCSocketAddress.ofService(123, 456).toSocatAddressString( + AFSocketType.SOCK_STREAM, AFSocketProtocol.DEFAULT); + assertNotNull(socatString); + } catch (SocketException e) { + if (AFSocket.supports(AFSocketCapability.CAPABILITY_TIPC)) { + throw e; + } else { + // expected + return; + } } + assertTrue(socatString.contains(":")); } } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/AFUNIXSocketAddressTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/AFUNIXSocketAddressTest.java index 828125a7f..cb11c834a 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/AFUNIXSocketAddressTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/AFUNIXSocketAddressTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,14 +19,21 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.net.URI; +import java.nio.ByteBuffer; import java.util.Arrays; import org.junit.jupiter.api.Test; +import org.newsclub.net.unix.pool.ObjectPool.Lease; import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; import com.kohlschutter.testutil.AssertUtil; @@ -133,4 +140,36 @@ public void testAbstractNamespace() throws Exception { assertNotNull(AFUNIXSocketAddress.inAbstractNamespace("test")); assertNotNull(AFUNIXSocketAddress.inAbstractNamespace("test", 1234)); } + + @Test + public void testSerialize() throws Exception { + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos)) { + + AFUNIXSocketAddress addr = AFUNIXSocketAddress.of(URI.create("file:/tmp/yo")); + oos.writeObject(addr); + oos.flush(); + + try (ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(bos + .toByteArray()))) { + AFUNIXSocketAddress addr2 = (AFUNIXSocketAddress) oin.readObject(); + assertEquals(addr, addr2); + assertEquals(addr.getAddressFamily(), addr2.getAddressFamily()); + } + } + } + + @Test + public void testCraftDeserialization() throws Exception { + AFUNIXSocketAddress sa1 = AFUNIXSocketAddress.ofNewTempFile(); + byte[] socketAddress = sa1.getBytes(); + try (Lease nativeAddressLease = sa1.getNativeAddressDirectBuffer()) { + int port = sa1.getPort(); + + AFUNIXSocketAddress sa2 = AFUNIXSocketAddress.newAFSocketAddress(port, socketAddress, + nativeAddressLease); + assertEquals(sa1, sa2); + assertNotEquals(AFUNIXSocketAddress.ofNewTempFile(), sa2); + } + } } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/AFVSOCKSocketAddressTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/AFVSOCKSocketAddressTest.java index 8a13033dd..b2d3901f0 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/AFVSOCKSocketAddressTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/AFVSOCKSocketAddressTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ package org.newsclub.net.unix; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -96,12 +96,19 @@ public void testNamedCIDs() throws Exception { @Test public void testSocatString() throws Exception { - String socatString = AFVSOCKSocketAddress.ofPortAndCID(123, 4).toSocatAddressString( - AFSocketType.SOCK_STREAM, AFSocketProtocol.DEFAULT); - if (socatString == null) { - assertFalse(AFSocket.supports(AFSocketCapability.CAPABILITY_VSOCK)); - } else { - assertTrue(socatString.contains(":")); + String socatString; + try { + socatString = AFVSOCKSocketAddress.ofPortAndCID(123, 4).toSocatAddressString( + AFSocketType.SOCK_STREAM, AFSocketProtocol.DEFAULT); + assertNotNull(socatString); + } catch (SocketException e) { + if (AFSocket.supports(AFSocketCapability.CAPABILITY_VSOCK)) { + throw e; + } else { + // expected + return; + } } + assertTrue(socatString.contains(":")); } } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/AcceptTimeoutTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/AcceptTimeoutTest.java index 58f14c1a0..a29341983 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/AcceptTimeoutTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/AcceptTimeoutTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ import java.net.SocketTimeoutException; import java.time.Duration; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.Test; @@ -39,10 +40,12 @@ import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; import com.kohlschutter.testutil.TestAbortedWithImportantMessageException; import com.kohlschutter.testutil.TestAbortedWithImportantMessageException.MessageType; +import com.kohlschutter.testutil.TestAsyncUtil; +import com.kohlschutter.testutil.TestStackTraceUtil; /** * Verifies that accept properly times out when an soTimeout was specified. - * + * * @author Christian Kohlschütter */ @SuppressFBWarnings({ @@ -117,53 +120,54 @@ public void testTimeoutAfterDelay() throws Exception { SocketAddress serverAddress = serverSock.getLocalSocketAddress(); serverAddressCF.complete(serverAddress); - new Thread() { - private final Socket socket = newSocket(); - - { - setDaemon(true); - } + final Socket threadSocket = newSocket(); + Thread t = new Thread(() -> { + int i = 0; + while (keepRunning.get()) { + i++; + try { + Thread.sleep(connectDelayMillis); + } catch (InterruptedException e) { + return; + } - @Override - public void run() { - int i = 0; - while (keepRunning.get()) { - i++; - try { - Thread.sleep(connectDelayMillis); - } catch (InterruptedException e) { + try { + connectSocket(threadSocket, serverAddress); + runtimeExceptionCF.complete(null); + } catch (SocketTimeoutException e) { + if (!keepRunning.get()) { return; } - try { - connectSocket(socket, serverAddress); - runtimeExceptionCF.complete(null); - } catch (SocketTimeoutException e) { - if (!keepRunning.get()) { - return; - } - - System.out.println("SocketTimeout, trying connect again (" + i + ")"); + System.out.println("SocketTimeout, trying connect again (" + i + ")"); + continue; + } catch (TestAbortedWithImportantMessageException e) { + runtimeExceptionCF.complete(e); + } catch (IOException e) { + // ignore "connection reset by peer", etc. after connection was accepted + if (!accepted.get()) { e.printStackTrace(); - continue; - } catch (TestAbortedWithImportantMessageException e) { - runtimeExceptionCF.complete(e); - } catch (IOException e) { - // ignore "connection reset by peer", etc. after connection was accepted - if (!accepted.get()) { - e.printStackTrace(); - } } - - break; // NOPMD.AvoidBranchingStatementAsLastInLoop } + + break; // NOPMD.AvoidBranchingStatementAsLastInLoop } - }.start(); + }); + t.setDaemon(true); + t.start(); long time = System.currentTimeMillis(); try (Socket socket = serverSock.accept();) { assertNotNull(socket); accepted.set(true); + } catch (SocketTimeoutException e) { + String msg = checkKnownBugAcceptTimeout(e); + if (msg == null) { + throw e; + } else { + throw new TestAbortedWithImportantMessageException( + MessageType.TEST_ABORTED_SHORT_WITH_ISSUES, msg, summaryImportantMessage(msg), e); + } } time = System.currentTimeMillis() - time; @@ -183,18 +187,29 @@ public void run() { if (msg == null) { throw e; } else { - throw new TestAbortedWithImportantMessageException(MessageType.TEST_ABORTED_WITH_ISSUES, - msg, e); + throw new TestAbortedWithImportantMessageException( + MessageType.TEST_ABORTED_SHORT_WITH_ISSUES, msg, summaryImportantMessage(msg), e); } } finally { keepRunning.set(false); } } + /** + * Subclasses may override this to tell that there is a known issue with "Accept timeout after + * delay" when a SocketTimeoutException was thrown. + * + * @param e The exception + * @return An explanation iff this should not cause a test failure but trigger "With issues". + */ + protected String checkKnownBugAcceptTimeout(SocketTimeoutException e) { + return null; + } + /** * Subclasses may override this to tell that there is a known issue with "Accept timeout after * delay". - * + * * @param serverAddr The server address. * @return An explanation iff this should not cause a test failure but trigger "With issues". */ @@ -205,6 +220,60 @@ protected String checkKnownBugAcceptTimeout(SocketAddress serverAddr) { @Test public void testAcceptWithoutBindToService() throws Exception { ServerSocket ss = newServerSocket(); - assertThrows(SocketException.class, () -> ss.accept()); + assertThrows(SocketException.class, () -> { + try (Socket unused = ss.accept()) { + fail("Should not be reached"); + } + }); + } + + @Test + public void testPendingAcceptCloseServerSocketImmediately() throws Exception { + testPendingAcceptCloseServerSocket(false); + } + + @Test + public void testPendingAcceptCloseServerSocketDelayed() throws Exception { + testPendingAcceptCloseServerSocket(true); + } + + private void testPendingAcceptCloseServerSocket(boolean delayed) throws Exception { + SocketAddress addr = newTempAddress(); + ServerSocket ss = newServerSocketBindOn(addr); + + Runnable doClose = () -> { + try { + ss.close(); + } catch (IOException e) { + TestStackTraceUtil.printStackTrace(e); + } + }; + + if (delayed) { + TestAsyncUtil.runAsyncDelayed(1, TimeUnit.SECONDS, doClose); + } else { + doClose.run(); + } + + try { + assertTimeoutPreemptively(Duration.ofSeconds(5), () -> { + // Should be SocketClosedException or InvalidArgumentSocketException, but no guarantee + assertThrows(SocketException.class, () -> { + try (Socket unused = ss.accept()) { + fail("Should not be reached"); + } catch (SocketException e) { + throw e; + } + }); + }); + } catch (AssertionFailedError e) { + String msg = checkKnownBugAcceptTimeout(addr); + if (msg == null) { + throw e; + } else { + throw new TestAbortedWithImportantMessageException( + MessageType.TEST_ABORTED_SHORT_WITH_ISSUES, msg, summaryImportantMessage(msg), e); + } + } } } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/AddressSpecifics.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/AddressSpecifics.java index 3e5d3a770..3c988ffbb 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/AddressSpecifics.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/AddressSpecifics.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,23 +31,27 @@ /** * Test-related methods to work with a particular {@link AFSocket} implementation. - * + * * It is essential to use these methods in tests instead of directly calling the {@link AFSocket} * etc. methods: Some socket implementations (and sometimes only in certain kernel/environment * configurations) may expose unexpected behavior that is otherwise hard to catch. - * + * * This is especially relevant when connecting/binding sockets. - * + * * @param The socket address. * @author Christian Kohlschütter * @see SocketTestBase */ public interface AddressSpecifics { + String addressFamilyString(); + Socket newStrictSocket() throws IOException; Socket newSocket() throws IOException; + SocketChannel newSocketChannel() throws IOException; + DatagramSocket newDatagramSocket() throws IOException; ServerSocket newServerSocket() throws IOException; @@ -104,8 +108,12 @@ default CloseablePair newInterconnectedSockets() throws IOExce ServerSocket server = newServerSocketBindOn(address); Socket client = connectTo(server.getLocalSocketAddress()); final Socket socket = server.accept(); - return new CloseablePair>((AFSocket) client, (AFSocket) socket, server); + return new CloseablePair<>(client, socket, server); } DatagramChannel newDatagramChannel() throws IOException; -} \ No newline at end of file + + ServerSocketChannel newServerSocketChannel() throws IOException; + + String summaryImportantMessage(String message); +} diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/AncillaryMessageTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/AncillaryMessageTest.java index 619066b12..8fdf3afaa 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/AncillaryMessageTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/AncillaryMessageTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/AvailableTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/AvailableTest.java index f954cc65f..e12551f5f 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/AvailableTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/AvailableTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/BufferOverflowTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/BufferOverflowTest.java index 467c63a13..b875f4467 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/BufferOverflowTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/BufferOverflowTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/BuildPropertiesTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/BuildPropertiesTest.java new file mode 100644 index 000000000..4ba5688a0 --- /dev/null +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/BuildPropertiesTest.java @@ -0,0 +1,56 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.kohlschutter.util.ExecutionEnvironmentUtil; + +public class BuildPropertiesTest { + @Test + public void testNotEmpty() throws Exception { + Map properties = BuildProperties.getBuildProperties(); + assertNotEquals(0, properties.size()); + } + + @Test + public void testHasProperties() throws Exception { + Map properties = BuildProperties.getBuildProperties(); + assertTrue(properties.containsKey("project.version")); + assertTrue(properties.containsKey("git.commit.id.full")); + } + + @Test + public void testResolved() throws Exception { + Map properties = BuildProperties.getBuildProperties(); + + String projectVersion = properties.get("project.version"); + if (ExecutionEnvironmentUtil.isInEclipse()) { + assertEquals("${project.version}", projectVersion); + } else { + assertNotEquals("", projectVersion); + assertNotEquals("${project.version}", projectVersion); + } + } +} diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/CancelAcceptTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/CancelAcceptTest.java index d40fdc774..77073f25f 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/CancelAcceptTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/CancelAcceptTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.Test; @@ -33,10 +34,11 @@ import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; import com.kohlschutter.testutil.TestAbortedWithImportantMessageException; import com.kohlschutter.testutil.TestAbortedWithImportantMessageException.MessageType; +import com.kohlschutter.testutil.TestStackTraceUtil; /** * Tests breaking out of accept. - * + * * @see Issue 6 */ @SuppressFBWarnings({ @@ -44,7 +46,6 @@ public abstract class CancelAcceptTest extends SocketTestBase { protected static final String NO_SOCKETEXCEPTION_CLOSED_SERVER = "Did not throw SocketException when connecting to closed server socket"; - private boolean serverSocketClosed = false; protected CancelAcceptTest(AddressSpecifics asp) { super(asp); @@ -52,7 +53,7 @@ protected CancelAcceptTest(AddressSpecifics asp) { @Test public void issue6test1() throws Exception { - serverSocketClosed = false; + AtomicBoolean serverSocketClosed = new AtomicBoolean(false); AtomicBoolean ignoreServerSocketClosedException = new AtomicBoolean(false); try (ServerThread serverThread = new ServerThread() { @@ -63,7 +64,7 @@ protected void handleConnection(final Socket sock) throws IOException { @Override protected void onServerSocketClose() { - serverSocketClosed = true; + serverSocketClosed.set(true); } @Override @@ -80,15 +81,17 @@ protected ExceptionHandlingDecision handleException(Exception e) { try (Socket sock = connectTo(serverThread.getServerAddress())) { // open and close + Objects.requireNonNull(sock); // silence Xlint warning } try (Socket sock = connectTo(serverThread.getServerAddress())) { // open and close + Objects.requireNonNull(sock); // silence Xlint warning } @SuppressWarnings("resource") final ServerSocket serverSocket = serverThread.getServerSocket(); - assertFalse(serverSocketClosed && !serverSocket.isClosed(), + assertFalse(serverSocketClosed.get() && !serverSocket.isClosed(), "ServerSocket should not be closed now"); // serverSocket.close() may throw a "Socket is closed" exception in the server thread @@ -98,9 +101,14 @@ protected ExceptionHandlingDecision handleException(Exception e) { SocketAddress serverAddress = serverThread.getServerAddress(); serverSocket.close(); + try { - try (Socket sock = connectTo(serverAddress)) { - // open and close + for (int i = 0; i < 2; i++) { + try (Socket unused = connectTo(serverAddress)) { + // open and close + } + // race condition: exception may be thrown only after a successful connect + // (seen with TIPC only) } String noticeNoSocketException = checkKnownConditionDidNotThrowSocketException(); @@ -114,11 +122,12 @@ protected ExceptionHandlingDecision handleException(Exception e) { // as expected } - assertTrue(serverSocketClosed || serverSocket.isClosed(), + assertTrue(serverSocketClosed.get() || serverSocket.isClosed(), "ServerSocket should be closed now"); try { try (Socket sock = connectTo(serverAddress)) { + Objects.requireNonNull(sock); // silence Xlint warning fail("ServerSocket should have been closed already"); } String noticeNoSocketException = checkKnownConditionDidNotThrowSocketException(); @@ -132,14 +141,14 @@ protected ExceptionHandlingDecision handleException(Exception e) { // as expected } } catch (SocketException e) { - e.printStackTrace(); + TestStackTraceUtil.printStackTrace(e); } } /** * Subclasses may override this to tell that there is a known condition where an otherwise * expected SocketException is not thrown. - * + * * @return An explanation iff this should not cause a test failure but just add a notice. */ protected String checkKnownConditionDidNotThrowSocketException() { diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/DatagramSocketTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/DatagramSocketTest.java index 5980c1bad..c5c910789 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/DatagramSocketTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/DatagramSocketTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,12 +33,17 @@ import java.net.SocketAddress; import java.net.SocketException; import java.net.SocketTimeoutException; +import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; import java.nio.charset.StandardCharsets; import java.time.Duration; import org.junit.jupiter.api.Test; +import org.opentest4j.AssertionFailedError; import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; +import com.kohlschutter.testutil.TestAbortedWithImportantMessageException; +import com.kohlschutter.testutil.TestAbortedWithImportantMessageException.MessageType; @AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DATAGRAMS) @SuppressFBWarnings({ @@ -108,7 +113,7 @@ private static void assertDatagramPacketAddress(DatagramPacket dp, SocketAddress } @Test - public void testBindConnect() throws SocketException, IOException, InterruptedException { + public void testBindConnect() throws Exception { AFSocketAddress ds1Addr = (AFSocketAddress) newTempAddressForDatagram(); AFSocketAddress ds2Addr = (AFSocketAddress) newTempAddressForDatagram(); @@ -130,7 +135,15 @@ public void testBindConnect() throws SocketException, IOException, InterruptedEx ds1.connect(ds2Addr); - assertConnectedDatagramSocket(ds1, ds1Addr, ds2Addr); + try { + assertConnectedDatagramSocket(ds1, ds1Addr, ds2Addr); + } catch (AssertionFailedError e) { + if (TestUtil.isHaikuOS()) { + throw TestUtil.haikuBug18534(e); + } else { + throw e; + } + } byte[] data = "Hello world!".getBytes(StandardCharsets.UTF_8); byte[] buf = new byte[512]; @@ -157,6 +170,20 @@ public void testBindConnect() throws SocketException, IOException, InterruptedEx assertEquals(12, dp1.getLength()); assertEquals(ds2Addr.wrapAddress(), dp1.getAddress()); + ByteBuffer bb1 = ByteBuffer.allocate(512); + ByteBuffer bb2 = ByteBuffer.allocateDirect(512); + + bb1.putLong(0xF00BAA); + bb1.flip(); + + ds1.getChannel().send(bb1, null); // can specify null here but no address, since it's already + // connected + + SocketAddress receivedFrom = ds2.getChannel().receive(bb2); + assertEquals(ds1Addr, receivedFrom); + bb2.flip(); + assertEquals(0xF00BAA, bb2.getLong()); + ds1.close(); assertClosedDatagramSocket(ds1); @@ -165,6 +192,45 @@ public void testBindConnect() throws SocketException, IOException, InterruptedEx } } + @Test + public void testChannelSendTo() throws Exception { + AFSocketAddress ds1Addr = (AFSocketAddress) newTempAddressForDatagram(); + AFSocketAddress ds2Addr = (AFSocketAddress) newTempAddressForDatagram(); + try (DatagramChannel dc1 = newDatagramChannel(); // + DatagramChannel dc2 = newDatagramChannel()) { + + // dc1 is neither connected nor bound + dc1.bind(ds1Addr); + dc2.bind(ds2Addr); + + ByteBuffer bb1 = ByteBuffer.allocate(512); + ByteBuffer bb2 = ByteBuffer.allocateDirect(512); + + bb1.putLong(0xF00BAA); + bb1.flip(); + + dc1.send(bb1, ds2Addr); + + SocketAddress receivedFrom = dc2.receive(bb2); + bb2.flip(); + assertEquals(0xF00BAA, bb2.getLong()); + + assertExpectedSocketAddressFromDatagramChannelReceive(ds1Addr, receivedFrom); + } + } + + @SuppressWarnings("PMD.PreserveStackTrace") + protected void assertExpectedSocketAddressFromDatagramChannelReceive(SocketAddress expected, + SocketAddress received) { + try { + assertEquals(expected, received); + } catch (AssertionFailedError e) { + throw new TestAbortedWithImportantMessageException(MessageType.TEST_ABORTED_SHORT_WITH_ISSUES, + "DatagramChannel.receive did not return expected the sender address; " + + "this may be a limitation of your system environment."); + } + } + @Test public void testReadTimeout() throws IOException { SocketAddress dsAddr = newTempAddressForDatagram(); diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/EndOfFileTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/EndOfFileTest.java index 37408db53..025fbab0b 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/EndOfFileTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/EndOfFileTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,7 @@ /** * See http://code.google.com/p/junixsocket/issues/detail?id=9 - * + * * @author Derrick Rice (April, 2010) */ @SuppressFBWarnings({ @@ -234,11 +234,11 @@ public void serverWriteToSocketClosedByClient() throws Exception { try { /* * http://www.unixguide.net/network/socketfaq/2.1.shtml http://www.faqs.org/rfcs/rfc793.html - * + * * The TCP RFC allows the open side to continue sending data. In most (all?) * implementations, the closed side will respond with a RST. For this reason, it takes two * writes to cause an IOException with TCP sockets. (or more, if there is latency) - * + * * However, it is expected that the write give an IOException as soon as possible - which * means it is OK for our socket implementation to give an IOException on the first write. */ diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/FileDescriptorCastTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/FileDescriptorCastTest.java index 6738a5c6e..bf0aa7be6 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/FileDescriptorCastTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/FileDescriptorCastTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -32,22 +34,31 @@ import java.io.OutputStream; import java.io.RandomAccessFile; import java.net.Socket; +import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; +import java.nio.channels.ServerSocketChannel; +import java.nio.file.Files; +import java.nio.file.Path; import java.time.Duration; +import java.util.Objects; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import org.eclipse.jdt.annotation.Nullable; import org.junit.jupiter.api.Test; -import org.opentest4j.AssertionFailedError; import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; +import com.kohlschutter.testutil.TestAbortedNotAnIssueException; import com.kohlschutter.util.IOUtil; @SuppressWarnings("PMD.CouplingBetweenObjects") @SuppressFBWarnings({ "THROWS_METHOD_THROWS_CLAUSE_THROWABLE", "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION"}) public class FileDescriptorCastTest { + // CPD-OFF + @Test public void testInvalidFileDescriptor() throws IOException { assertThrows(IOException.class, () -> FileDescriptorCast.using(new FileDescriptor())); @@ -82,17 +93,40 @@ public void testStdout() throws IOException { // We can cast this file descriptor to an InputStream, but read access will fail assertEquals(fdc.as(InputStream.class).getClass(), fdc.as(FileInputStream.class).getClass()); - try { - assertTimeoutPreemptively(Duration.ofMillis(100), () -> { - try { - fdc.as(InputStream.class).read(); - } catch (IOException e) { - // expected, but not guaranteed (Linux won't throw this); ignore - } - }); - } catch (AssertionFailedError e) { - // on Linux, we timeout, and that's OK, too. - } + // Below we see code that may or may not block indefinitely due to system constraints + // There's not much we can do other than not run it (it doesn't check state anyhow). + + // try { + // assertTimeoutPreemptively(Duration.ofMillis(100), () -> { + // try { + // fdc.as(InputStream.class).read(); + // } catch (IOException e) { + // // expected, but not guaranteed (Linux won't throw this); ignore + // } + // }); + // } catch (AssertionFailedError e) { + // // on Linux, we timeout, and that's OK, too. + // } + } + + @Test + public void testCastAsInteger() throws Exception { + assertNotEquals(-1, FileDescriptorCast.using(FileDescriptor.in).as(Integer.class)); + assertNotEquals(-1, FileDescriptorCast.using(FileDescriptor.out).as(Integer.class)); + assertNotEquals(-1, FileDescriptorCast.using(FileDescriptor.err).as(Integer.class)); + + assertThrows(IOException.class, () -> FileDescriptorCast.using(new FileDescriptor()).as( + Integer.class)); + } + + @Test + @AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNSAFE) + public void testUnsafeCast() throws Exception { + assertEquals(1, FileDescriptorCast.unsafeUsing(1).as(Integer.class)); + assertEquals(-2, FileDescriptorCast.unsafeUsing(-2).as(Integer.class)); + assertSame(FileDescriptor.out, FileDescriptorCast.unsafeUsing(FileDescriptorCast.using( + FileDescriptor.out).as(Integer.class)).as(FileDescriptor.class)); + assertThrows(IOException.class, () -> FileDescriptorCast.unsafeUsing(-1).as(Integer.class)); } @Test @@ -165,4 +199,194 @@ public void testPipe() throws Exception { }); } } + + @AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DOMAIN) + @Test + public void testCastGeneric() throws Exception { + try (AFUNIXServerSocketChannel ussc = AFUNIXServerSocketChannel.open()) { + ussc.bind(AFUNIXSocketAddress.ofNewTempFile()); + + AFGenericServerSocketChannel gssc = FileDescriptorCast.using(ussc.getFileDescriptor()).as( + AFGenericServerSocketChannel.class); + + CompletableFuture<@Nullable ConnectionResult> cf = CompletableFuture.supplyAsync(() -> { + try { + AFGenericSocketChannel sc = gssc.accept(); + ByteBuffer bb = ByteBuffer.allocate(64); + int r = sc.read(bb); + if (r != 1) { + throw new IllegalStateException("Unexpected result: " + r + " bytes read"); + } + + return new ConnectionResult(bb.get(0), sc.getLocalSocketAddress(), sc + .getRemoteSocketAddress()); + } catch (IOException e) { + throw new IllegalStateException(e); + } + }); + + try (AFUNIXSocket sock = AFUNIXSocket.connectTo(ussc.getLocalAddress())) { + AFGenericSocket gs = FileDescriptorCast.using(sock.getFileDescriptor()).as( + AFGenericSocket.class); + + try { + gs.getOutputStream().write(123); + } catch (BrokenPipeSocketException e) { + try { + cf.get(); + } catch (Exception e2) { + e.addSuppressed(e2); + } + throw e; + } + + AFGenericSocketAddress lsa = gs.getLocalSocketAddress(); + if (lsa != null) { + assertEquals(AFGenericSocketAddress.class, lsa.getClass()); + } + AFGenericSocketAddress rsa = gs.getRemoteSocketAddress(); + if (rsa != null) { + assertEquals(AFGenericSocketAddress.class, rsa.getClass()); + } + + ConnectionResult cr = Objects.requireNonNull(cf.get()); + assertEquals(123, cr.firstByte); + if (lsa != null) { + compareGenericAddresses(lsa, cr.remoteSocketAddress); + } + if (rsa != null) { + compareGenericAddresses(rsa, cr.localSocketAddress); + } + } + } + } + + private void compareGenericAddresses(AFGenericSocketAddress rsa, SocketAddress lsa) { + if (rsa.toBytes().length < 2 && !rsa.equals(lsa)) { + // observed on AIX + // this is more or less equivalent to rsa == null + } else { + if (!rsa.equals(lsa) && lsa instanceof AFGenericSocketAddress) { + // On Windows, we may get the same address back but padding bytes + // may be omitted. Therefore, just compare the common prefix. + + byte[] bytesRemote = rsa.getBytes(); + byte[] bytesLocal = ((AFGenericSocketAddress) lsa).getBytes(); + int minLength = Math.min(bytesRemote.length, bytesLocal.length); + + boolean ok = true; + for (int i = 0; i < minLength; i++) { + if (bytesRemote[i] != bytesLocal[i]) { + ok = false; + break; + } + } + if (!ok) { + assertEquals(rsa, lsa); + } + } else { + assertEquals(rsa, lsa); + } + } + } + + @AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DOMAIN) + @Test + public void testCastGenericDuplicating() throws Exception { + AFUNIXSocketAddress addr = AFUNIXSocketAddress.ofNewTempFile(); + Path p = addr.getFile().toPath(); + try (AFUNIXServerSocketChannel ussc = AFUNIXServerSocketChannel.open()) { + ussc.bind(addr); + + FileDescriptorCast fdc = FileDescriptorCast.duplicating(ussc.getFileDescriptor()); + if (fdc == null) { + throw new TestAbortedNotAnIssueException("FileDescriptCast.duplicating not supported"); + } + + // also won't delete the file because we told to it not delete above, otherwise + // we would be able to bind but not connect (this is an AF_UNIX-specific issue) + + AFGenericServerSocketChannel gssc = fdc.as(AFGenericServerSocketChannel.class); + + CompletableFuture<@Nullable ConnectionResult> cf = CompletableFuture.supplyAsync(() -> { + try { + AFGenericSocketChannel sc = gssc.accept(); + ByteBuffer bb = ByteBuffer.allocate(64); + int r = sc.read(bb); + if (r != 1) { + throw new IllegalStateException("Unexpected result: " + r + " bytes read"); + } + + return new ConnectionResult(bb.get(0), sc.getLocalSocketAddress(), sc + .getRemoteSocketAddress()); + } catch (IOException e) { + throw new IllegalStateException(e); + } + }); + + try (AFUNIXSocket sock = AFUNIXSocket.connectTo(ussc.getLocalAddress())) { + AFGenericSocket gs = FileDescriptorCast.using(sock.getFileDescriptor()).as( + AFGenericSocket.class); + + try { + gs.getOutputStream().write(123); + } catch (BrokenPipeSocketException e) { + try { + cf.get(); + } catch (Exception e2) { + e.addSuppressed(e2); + } + throw e; + } + + AFGenericSocketAddress lsa = gs.getLocalSocketAddress(); + if (lsa != null) { + assertEquals(AFGenericSocketAddress.class, lsa.getClass()); + } + AFGenericSocketAddress rsa = gs.getRemoteSocketAddress(); + if (rsa != null) { + assertEquals(AFGenericSocketAddress.class, rsa.getClass()); + } + + ConnectionResult cr = Objects.requireNonNull(cf.get()); + assertEquals(123, cr.firstByte); + if (lsa != null) { + compareGenericAddresses(lsa, cr.remoteSocketAddress); + } + if (rsa != null) { + compareGenericAddresses(rsa, cr.localSocketAddress); + } + } + } finally { + Files.deleteIfExists(p); + } + } + + private static final class ConnectionResult { + private final int firstByte; + private final SocketAddress localSocketAddress; + private final SocketAddress remoteSocketAddress; + + public ConnectionResult(int firstByte, AFGenericSocketAddress localSocketAddress, + AFGenericSocketAddress remoteSocketAddress) { + this.firstByte = firstByte; + this.localSocketAddress = localSocketAddress; + this.remoteSocketAddress = remoteSocketAddress; + } + } + + @AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DOMAIN) + @Test + public void testCastToServerSocketIsSameType() throws Exception { + AFUNIXServerSocketChannel ussc = AFUNIXServerSocketChannel.open(); + ussc.bind(AFUNIXSocketAddress.ofNewTempFile()); + + ServerSocketChannel ssc = FileDescriptorCast.using(ussc.getFileDescriptor()).as( + ServerSocketChannel.class); + assertEquals(AFUNIXServerSocketChannel.class, ssc.getClass()); + + AFUNIXSocket us = AFUNIXSocket.connectTo(ussc.getLocalAddress()); + Socket s = FileDescriptorCast.using(us.getFileDescriptor()).as(Socket.class); + assertEquals(AFUNIXSocket.class, s.getClass()); + } } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/FinalizeTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/FinalizeTest.java index 03eab2f38..3bf911f48 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/FinalizeTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/FinalizeTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,14 +44,13 @@ /** * This tests the issue reported in * issue 29. - * + * * We need to ensure that the native file descriptor is closed whenever our socket implementation is * garbage collected, even when {@link AFSocket#close()} is not called. - * + * * @author Christian Kohlschütter */ @CommandAvailabilityRequirement(commands = {"lsof"}) -@ForkedVMRequirement(forkSupported = true) @SuppressFBWarnings({ "THROWS_METHOD_THROWS_CLAUSE_THROWABLE", "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION"}) public abstract class FinalizeTest extends SocketTestBase { @@ -61,6 +60,7 @@ protected FinalizeTest(AddressSpecifics asp) { super(asp); } + @ForkedVMRequirement(forkSupported = true) @Test public void testLeak() throws Exception { assertTimeoutPreemptively(Duration.ofSeconds(10), () -> { @@ -78,10 +78,12 @@ protected void handleConnection(final Socket socket) throws IOException { try { assumeTrue(process.pid() > 0); Object preRunCheck = null; - try (OutputStream out = socket.getOutputStream(); - InputStream in = socket.getInputStream()) { - preRunCheck = preRunCheck(process); - out.write('@'); + try { + try (OutputStream out = socket.getOutputStream(); + InputStream unused = socket.getInputStream()) { + preRunCheck = preRunCheck(process); + out.write('@'); + } } finally { future.complete(preRunCheck); } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/FinalizeTestClient.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/FinalizeTestClient.java index 19673a531..1088bf5e6 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/FinalizeTestClient.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/FinalizeTestClient.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,22 +19,25 @@ import java.net.Socket; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; /** * A potentially file descriptor-leaking client. - * + * * See issue 29 for details. - * + * * @see FinalizeTest * @author Christian Kohlschütter */ @SuppressFBWarnings({ "THROWS_METHOD_THROWS_CLAUSE_THROWABLE", "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION"}) public class FinalizeTestClient { - @SuppressFBWarnings({"RV_RETURN_VALUE_IGNORED"}) + @SuppressWarnings({"ModifiedButNotUsed" /* errorprone */}) + @SuppressFBWarnings({"RV_RETURN_VALUE_IGNORED", "UC_USELESS_OBJECT"}) public static void main(String[] args) throws Exception { String socketType = System.getProperty("test.junixsocket.socket.type", ""); String socketName = System.getProperty("test.junixsocket.socket", ""); @@ -56,9 +59,17 @@ public static void main(String[] args) throws Exception { } socket = AFSocket.connectTo(Objects.requireNonNull(addr)); socket.getInputStream().read(); + + // create some pressure on GC + List list = new ArrayList<>(); + long nextGc = System.currentTimeMillis() + 500; while (true) { - // create some pressure on GC - new String("junixsocket".getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); + list.add(new String("junixsocket".getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8)); + long now = System.currentTimeMillis(); + if (now >= nextGc) { + nextGc = now + 500; + System.gc(); // NOPMD + } } } } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/ImplUtil.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/ImplUtil.java index ce2a174d2..48d532f67 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/ImplUtil.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/ImplUtil.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/InetAddressTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/InetAddressTest.java index b753fbb5c..310b4e962 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/InetAddressTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/InetAddressTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/InterruptIssue158Test.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/InterruptIssue158Test.java new file mode 100644 index 000000000..491319f5e --- /dev/null +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/InterruptIssue158Test.java @@ -0,0 +1,382 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.net.BindException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedByInterruptException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Predicate; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import com.kohlschutter.testutil.TestAbortedNotAnIssueException; +import com.kohlschutter.util.SystemPropertyUtil; + +/** + * Test interrupt-related behavior, as discussed in + * issue 158. + * + * @author https://github.com/cenodis + * @author Christian Kohlschütter + */ +@SuppressWarnings({"PMD", "exports"}) +@TestInstance(Lifecycle.PER_CLASS) +public abstract class InterruptIssue158Test extends SocketTestBase { + // enable for additional debugging to System.out + private static boolean DEBUG = SystemPropertyUtil.getBooleanSystemProperty( + "selftest.issue.158.debug", false); + private static boolean DEBUG_VERBOSE = (System.getProperty("com.kohlschutter.selftest") == null) + && SystemPropertyUtil.getBooleanSystemProperty("selftest.issue.158.debug.verbose", true); + + private static boolean DELAY_CLOSE = SystemPropertyUtil.getBooleanSystemProperty( + "selftest.issue.158.delay-close", true); + + private A address = newAddress(); + private TestInfo testInfo; + private List closeables = new ArrayList<>(); + + protected InterruptIssue158Test(AddressSpecifics asp) { + super(asp); + } + + @BeforeEach + public void beforeEach(TestInfo info) { + this.testInfo = info; + this.address = newAddress(); + } + + @SuppressWarnings("unchecked") + private A newAddress() { + try { + return (A) newTempAddress(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void closeAfterTest() { + deleteSocketFile(address); + + for (AutoCloseable cl : closeables) { + try { + cl.close(); + } catch (Exception e) { + // ignore + } + } + closeables.clear(); + } + + @AfterEach + public void afterEach() { + closeAfterTest(); + } + + protected abstract void deleteSocketFile(A sa); + + public List clientProvider() { + return Arrays.asList( // + // variants + socket(false, this::newSocket, s -> s.connect(address), SocketException.class, + Socket::isClosed), // + socket(true, () -> newConnectedSocket(address), s -> s.getInputStream().read(), + SocketException.class, Socket::isClosed), // + socket(true, () -> newConnectedSocket(address), s -> s.getOutputStream().write(10), + SocketException.class, Socket::isClosed), // + socket(false, this::newSocketChannel, s -> s.connect(address), + ClosedByInterruptException.class, s -> !s.isOpen()), // + socket(true, this::connectSocketChannel, s -> s.read(ByteBuffer.allocate(1)), + ClosedByInterruptException.class, s -> !s.isOpen()), // + socket(true, this::connectSocketChannel, s -> s.write(ByteBuffer.allocate(1)), + ClosedByInterruptException.class, s -> !s.isOpen()) // + ); + } + + public List serverProvider() { + return Arrays.asList( // + serverSocket(() -> registerCloseable(newServerSocketBindOn(address)), ServerSocket::accept, + SocketException.class, ServerSocket::isClosed), // + serverSocket(this::bindServerSocketChannel, ServerSocketChannel::accept, + ClosedByInterruptException.class, s -> !s.isOpen())// + ); + } + + @ParameterizedTest(name = "variant {index}") + @MethodSource("clientProvider") + public void testClientInterruption(boolean acceptConnections, + IOSupplier socket, IOConsumer blockingOp, Class expectedException, + Predicate closeCheck) throws Throwable { + withServer(acceptConnections, () -> testSocketInterruption(false, socket, blockingOp, + expectedException, closeCheck)); + } + + @ParameterizedTest(name = "variant {index}") + @MethodSource("clientProvider") + public void testClientInterruptionWithDelay(boolean acceptConnections, + IOSupplier socket, IOConsumer blockingOp, Class expectedException, + Predicate closeCheck) throws Throwable { + withServer(acceptConnections, () -> testSocketInterruption(true, socket, blockingOp, + expectedException, closeCheck)); + } + + @ParameterizedTest(name = "variant {index}") + @MethodSource("serverProvider") + public void testServerInterruption(IOSupplier socket, + IOConsumer blockingOp, Class expectedException, Predicate closeCheck) + throws Throwable { + testSocketInterruption(false, socket, blockingOp, expectedException, closeCheck); + } + + @ParameterizedTest(name = "variant {index}") + @MethodSource("serverProvider") + public void testServerInterruptionWithDelay(IOSupplier socket, + IOConsumer blockingOp, Class expectedException, Predicate closeCheck) + throws Throwable { + testSocketInterruption(true, socket, blockingOp, expectedException, closeCheck); + } + + public void testSocketInterruption(boolean delay, IOSupplier socket, + IOConsumer blockingOp, Class expectedException, Predicate closeCheck) + throws Throwable { + AtomicReference exceptionHolder = new AtomicReference<>(); + CountDownLatch ready = new CountDownLatch(1); + Thread t = ThreadUtil.startNewDaemonThread(true, () -> exceptionHolder.set(runOperation(ready, + socket, blockingOp, expectedException, closeCheck))); + + ready.await(); + if (delay) { + Thread.sleep(500); + } + t.interrupt(); + t.join(Duration.of(1, ChronoUnit.SECONDS).toMillis()); + if (t.isAlive()) { + // Thread.interrupt is not guaranteed to succeed + // observed with graalvm-jdk-17.0.9+11.1 when building with agent for junixsocket-native-graalvm + // What we need to do here is to close all socket-related resources and try again + closeAfterTest(); + t.interrupt(); + t.join(Duration.of(1, ChronoUnit.SECONDS).toMillis()); + if (t.isAlive()) { + throw new RuntimeException("Thread failed to terminate after interrupt"); + } + } + Throwable thrownException = exceptionHolder.get(); + if (thrownException != null) { + throw thrownException; + } + } + + private C registerCloseable(C closeable) { + closeables.add(closeable); + return closeable; + } + + private void withServer(boolean acceptConnections, ThrowingRunnable func) throws Throwable { + Semaphore done = new Semaphore(0); + try (ServerSocketChannel serverSocket = registerCloseable(newServerSocketChannel())) { + serverSocket.bind(address); + Thread serverThread = null; + if (acceptConnections) { + serverThread = ThreadUtil.startNewDaemonThread(false, () -> { + while (serverSocket.isOpen()) { + SocketChannel socket = null; + try { + socket = serverSocket.accept(); + } catch (ClosedChannelException e) { + return; + } catch (IOException e) { + throw new RuntimeException("Unable to accept socket ", e); + } finally { + if (socket != null) { + final SocketChannel socketToClose = socket; + if (DELAY_CLOSE) { + CompletableFuture.runAsync(() -> { + try { + if (!done.tryAcquire(1, TimeUnit.SECONDS)) { + // ignore + } + } catch (InterruptedException e) { + // ignore + } + try { + socketToClose.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + }); + } else { + try { + socketToClose.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + } + } + }); + } + try { + func.run(); + } finally { + done.release(); + serverSocket.close(); + if (serverThread != null) { + serverThread.join(); + } + } + } + } + + Throwable runOperation(CountDownLatch ready, IOSupplier socket, + IOConsumer blockingOp, Class expectedException, Predicate closeCheck) { + + boolean supported = false; + Exception exc = null; + try { + @SuppressWarnings({"resource"}) + T sock = registerCloseable(socket.get()); + ready.countDown(); + + supported = true; + try { + blockingOp.accept(sock); + } catch (Exception e) { + exc = e; + // These tests usually expect the "Thread interrupted" state to be set. + // However, when we accept any SocketException to be thrown, that state is not + // deterministic. + // Also, when we expect any kind of ClosedChannelException, it is only expected to be + // set when the actual exception thrown is from the ClosedByInterruptException subclass. + boolean ignoreInterruptState = SocketException.class.equals(expectedException); + boolean interruptStateOK = Thread.interrupted() || (ClosedChannelException.class + .isAssignableFrom(expectedException) && !(e instanceof ClosedByInterruptException)); + + if (!expectedException.isAssignableFrom(e.getClass())) { + // log unexpected exception stack trace + e.printStackTrace(); + } + + assertAll(() -> assertInstanceOf(expectedException, e, "Socket exception"), + () -> assertTrue(ignoreInterruptState || interruptStateOK, "Thread interrupted"), + () -> assertTrue(closeCheck.test(sock), "Socket closed")); + } finally { + ready.countDown(); + if (sock != null) { + try { + sock.close(); + } catch (Exception e) { + throw new RuntimeException("Unable to clean up socket", e); + } + } + } + } catch (TestAbortedNotAnIssueException e) { + return e; + } catch (Throwable e) { + e.printStackTrace(); + return e; + } finally { + ready.countDown(); + if (DEBUG) { + // print concise results for debugging: + if (DEBUG_VERBOSE) { + System.out.print(testInfo.getTestClass().get().getName() + "." + testInfo.getTestMethod() + .get().getName() + " " + testInfo.getDisplayName() + ": "); + } + System.out.println((supported ? (exc == null ? "no exception" : exc) : "unsupported")); + } + } + return null; + } + + private static Arguments socket(boolean acceptConnections, IOSupplier socket, + IOConsumer blockingOp, Class expectedException, Predicate closeCheck) { + return Arguments.of(acceptConnections, socket, blockingOp, expectedException, closeCheck); + } + + private static Arguments serverSocket(IOSupplier socket, IOConsumer blockingOp, + Class expectedException, Predicate closeCheck) { + return Arguments.of(socket, blockingOp, expectedException, closeCheck); + } + + private SocketChannel connectSocketChannel() throws IOException { + SocketChannel socket = registerCloseable(newSocketChannel()); + socket.connect(address); + return socket; + } + + private ServerSocketChannel bindServerSocketChannel() throws IOException { + ServerSocketChannel socket = registerCloseable(newServerSocketChannel()); + try { + try { + socket.bind(address); + } catch (BindException e) { + // With Inet sockets, our reserved address may just have been taken by another process + // so let's try again + address = newAddress(); + socket.bind(address); + } + } catch (BindException e) { + throw (BindException) new BindException(e.getMessage() + ": " + address).initCause(e); + } + return socket; + } + + private interface IOSupplier { + T get() throws IOException; + } + + private interface IOConsumer { + void accept(T t) throws IOException; + } + + private interface ThrowingRunnable { + void run() throws Throwable; + } +} diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/InterruptTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/InterruptTest.java new file mode 100644 index 000000000..c17112c9c --- /dev/null +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/InterruptTest.java @@ -0,0 +1,137 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.channels.ClosedByInterruptException; +import java.nio.channels.ServerSocketChannel; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import com.kohlschutter.testutil.TestAbortedNotAnIssueException; + +@TestMethodOrder(MethodOrderer.MethodName.class) +public abstract class InterruptTest extends SocketTestBase { + protected InterruptTest(AddressSpecifics asp) { + super(asp); + } + + @Test + public void testInterruptSocketVirtualThread() throws Exception { + if (!ThreadUtil.isVirtualThreadSupported()) { + throw new TestAbortedNotAnIssueException("Virtual Threads are not supported by this JVM"); + } + + try (ServerSocket serverSocket = newServerSocketBindOn(newTempAddress())) { + CompletableFuture myThread = new CompletableFuture<>(); + CompletableFuture result = new CompletableFuture<>(); + + ExecutorService executor = ThreadUtil.newVirtualThreadPerTaskExecutor(); + executor.submit(() -> { + myThread.complete(Thread.currentThread()); + boolean closedByInterrupt = false; + try { + serverSocket.accept(); + } catch (SocketClosedByInterruptException e) { + if (Thread.interrupted()) { + closedByInterrupt = true; + } else { + e.printStackTrace(); + } + } catch (SocketException e) { + if ("Closed by interrupt".equals(e.getMessage()) && Thread.interrupted() && serverSocket + .isClosed()) { + closedByInterrupt = true; + } else { + e.printStackTrace(); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + result.complete(closedByInterrupt); + } + }); + + Thread t = myThread.get(); + t.interrupt(); + assertTrue(result.get(), + "Thread should have thrown a \"Closed by interrupt\" SocketException," + + " the server socket should be closed, and the interrupt state should be set"); + + executor.shutdownNow(); + assertTrue(executor.awaitTermination(1, TimeUnit.SECONDS)); + } + } + + @Test + public void testInterruptSocketChannelVirtualThread() throws Exception { + if (!ThreadUtil.isVirtualThreadSupported()) { + throw new TestAbortedNotAnIssueException("Virtual Threads are not supported by this JVM"); + } + + assertTimeoutPreemptively(Duration.ofSeconds(10), () -> { + try (ServerSocketChannel serverSocketChannel = newServerSocketChannelBindOn( + newTempAddress())) { + serverSocketChannel.configureBlocking(true); + + CompletableFuture myThread = new CompletableFuture<>(); + CompletableFuture result = new CompletableFuture<>(); + + ExecutorService executor = ThreadUtil.newVirtualThreadPerTaskExecutor(); + executor.submit(() -> { + myThread.complete(Thread.currentThread()); + boolean closedByInterrupt = false; + try { + serverSocketChannel.accept(); + } catch (ClosedByInterruptException e) { + if (Thread.currentThread().isInterrupted()) { + closedByInterrupt = true; + } else { + e.printStackTrace(); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + result.complete(closedByInterrupt); + } + }); + + Thread t = myThread.get(); + t.interrupt(); + // assertTrue(result.get(), + // "Thread should have thrown a \"Closed by interrupt\" SocketException, and the interrupt + // state should be set"); + + executor.shutdownNow(); + assertTrue(executor.awaitTermination(1, TimeUnit.SECONDS)); + } + }); + } +} diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/MassiveParallelTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/MassiveParallelTest.java new file mode 100644 index 000000000..e987288a6 --- /dev/null +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/MassiveParallelTest.java @@ -0,0 +1,30 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import java.net.SocketAddress; + +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.TestMethodOrder; + +@TestMethodOrder(MethodOrderer.MethodName.class) +public abstract class MassiveParallelTest extends SocketTestBase { + protected MassiveParallelTest(AddressSpecifics asp) { + super(asp); + } +} diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/PipeTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/PipeTest.java index 8a7b6e580..db7ec132a 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/PipeTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/PipeTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,7 @@ /** * Tests the behavior of {@link AFPipe}. - * + * * @author Christian Kohlschütter */ @SuppressFBWarnings("THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION") @@ -47,7 +47,7 @@ public final class PipeTest { /** * Tests sequential writing/reading. - * + * * @throws IOException on error. */ @Test @@ -78,7 +78,7 @@ public void testPipe() throws IOException { /** * Tests concurrent writing/reading from the pipe. - * + * * @throws IOException on error. * @throws InterruptedException on error. * @throws ExecutionException on error. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/ReadWriteTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/ReadWriteTest.java index f18a685aa..171a78f7c 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/ReadWriteTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/ReadWriteTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,7 @@ /** * Reads and writes data either using byte arrays or byte-for-byte (which may be implemented * differently). - * + * * @author Christian Kohlschütter */ @SuppressFBWarnings({ @@ -90,7 +90,7 @@ public void testReceiveDataByteForByteSendByteForByte() { } private final class ByteArrayWritingServerThread extends ServerThread { - public ByteArrayWritingServerThread() throws IOException { + public ByteArrayWritingServerThread() throws IOException, InterruptedException { super(); } @@ -110,7 +110,7 @@ protected void handleConnection(final Socket sock) throws IOException { } private final class ByteForByteWritingServerThread extends ServerThread { - public ByteForByteWritingServerThread() throws IOException { + public ByteForByteWritingServerThread() throws IOException, InterruptedException { super(); } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/SelectorTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/SelectorTest.java index 9e67e4af0..c055e91f5 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/SelectorTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/SelectorTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -175,11 +175,11 @@ private Future newHelloClient(SocketAddress serverAddr, Semaphore sema) /** * Tests a non-blocking server setup where a client connects, sends "Hello" to the server and then * disconnects after the server has received the message. - * + * * In the selection loop, after the client has disconnected, the server should see that the * corresponding {@link SelectionKey} is "readable" (even if that means the connection was * closed). - * + * * @throws Exception on error. */ @Test @@ -193,7 +193,7 @@ public void testConnectionCloseEventualClientDisconnect() throws Exception { * * Unlike {@link #testConnectionCloseEventualClientDisconnect()}, this may uncover additional * faults, such as an unexpected exception when trying to read the socket name. - * + * * @throws Exception on error. */ @Test @@ -204,11 +204,11 @@ public void testConnectionCloseImmediateClientDisconnect() throws Exception { /** * Tests a non-blocking server setup where a client connects, sends "Hello" to the server and then * disconnects after the server has received the message. - * + * * In the selection loop, after the client has disconnected, the server should see that the * corresponding {@link SelectionKey} is "readable" (even if that means the connection was * closed). - * + * * @throws Exception on error. */ @Test @@ -222,7 +222,7 @@ public void testConnectionCloseEventualClientDisconnectKeepLooping() throws Exce * * Unlike {@link #testConnectionCloseEventualClientDisconnect()}, this may uncover additional * faults, such as an unexpected exception when trying to read the socket name. - * + * * @throws Exception on error. */ @Test @@ -253,10 +253,12 @@ private void testConnectionClose(boolean clientCloseImmediately, boolean checkIn int numReadable = 0; int numClosedChannelException = 0; - int timeout = 5000; + long timeout = 5000; selectLoop : while (timeout > 0) { long time = System.currentTimeMillis(); - while (selector.select(timeout) != 0) { + int n; + while ((n = selector.select(timeout)) != 0) { + assertTrue(n > 0); for (SelectionKey key : selector.selectedKeys()) { if (numAcceptable > 3 || numReadable > 2) { break selectLoop; @@ -324,7 +326,7 @@ private void testConnectionClose(boolean clientCloseImmediately, boolean checkIn } catch (ExecutionException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { - throw (RuntimeException) t; + throw (RuntimeException) t; // NOPMD.PreserveStackTrace } } } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/SelftestDiagnosticsHelper.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/SelftestDiagnosticsHelper.java index bc0544efa..7c19ec925 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/SelftestDiagnosticsHelper.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/SelftestDiagnosticsHelper.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,11 @@ package org.newsclub.net.unix; import java.io.File; +import java.util.Map; /** * Some bridging code that allows junixsocket-selftest to do some in-depth diagnostics. - * + * * @author Christian Kohlschütter */ public final class SelftestDiagnosticsHelper { @@ -30,7 +31,7 @@ private SelftestDiagnosticsHelper() { /** * Returns the error that prevented the native library from loading, or {@code null}. - * + * * @return The error, or {@code null}. */ public static Throwable initError() { @@ -39,10 +40,21 @@ public static Throwable initError() { /** * Returns the temporary directory used for storing the native library, or {@code null}. - * + * * @return The directory, or {@code null}. */ public static File tempDir() { return NativeLibraryLoader.tempDir(); } + + /** + * Returns properties determined upon Maven build time. + * + * For performance reasons, these will not be correctly resolves when developing in Eclipse. + * + * @return The properties. + */ + public static Map buildProperties() { + return BuildProperties.getBuildProperties(); + } } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/SelftestProvider.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/SelftestProvider.java index 0c8412221..3744eda4b 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/SelftestProvider.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/SelftestProvider.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,13 @@ */ package org.newsclub.net.unix; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; import java.net.InetSocketAddress; -import java.util.Collections; +import java.net.SocketAddress; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; @@ -27,20 +32,24 @@ /** * Provides references to all "junixsocket-common" tests that should be included in * junixsocket-selftest. - * + * * @author Christian Kohlschütter */ @SuppressWarnings("PMD.CouplingBetweenObjects") public class SelftestProvider { - final Map>> testMap = new LinkedHashMap<>(); + private static final String COMMON = "junixsocket-common"; + private static final String COMMON_JAVA_INET = "junixsocket-common.JavaInet"; + private static final String COMMON_JEP380 = "junixsocket-common.JEP380"; + + final Map>> testMap = new LinkedHashMap<>(); // NOPMD.LooseCoupling // CPD-OFF @SuppressWarnings({"PMD.ExcessiveMethodLength", "PMD.UnnecessaryFullyQualifiedName"}) public SelftestProvider() { - registerTest("junixsocket-common", org.newsclub.net.unix.AFTIPCSocketAddressTest.class); + registerTest(COMMON, org.newsclub.net.unix.AFTIPCSocketAddressTest.class); - registerTest("junixsocket-common", org.newsclub.net.unix.AFUNIXSocketAddressTest.class); + registerTest(COMMON, org.newsclub.net.unix.AFUNIXSocketAddressTest.class); registerTest(org.newsclub.net.unix.domain.AbstractNamespaceTest.class); @@ -50,6 +59,8 @@ public SelftestProvider() { registerTest(org.newsclub.net.unix.domain.AvailableTest.class); + registerTest(COMMON, BuildPropertiesTest.class); + registerTest(org.newsclub.net.unix.domain.BufferOverflowTest.class); registerTest(org.newsclub.net.unix.domain.CancelAcceptTest.class); @@ -58,8 +69,8 @@ public SelftestProvider() { registerTest(org.newsclub.net.unix.domain.EndOfFileTest.class); - registerTest("junixsocket-common", org.newsclub.net.unix.FileDescriptorCastTest.class); - registerTest("junixsocket-common", org.newsclub.net.unix.domain.FileDescriptorCastTest.class); + registerTest(COMMON, org.newsclub.net.unix.FileDescriptorCastTest.class); + registerTest(COMMON, org.newsclub.net.unix.domain.FileDescriptorCastTest.class); // file-descriptor passing is AF_UNIX-specific registerTest(org.newsclub.net.unix.domain.FileDescriptorsTest.class); @@ -68,10 +79,19 @@ public SelftestProvider() { registerTest(InetAddressTest.class); + registerTest(org.newsclub.net.unix.domain.InterruptTest.class); + registerTest(org.newsclub.net.unix.domain.InterruptIssue158Test.class); + registerTestJavaInet(org.newsclub.net.unix.java.InterruptIssue158Test.class); + registerTestJEP380(org.newsclub.net.unix.jep380.InterruptIssue158Test.class); + + registerTestJavaInet(org.newsclub.net.unix.java.InterruptTest.class); + + registerTest(org.newsclub.net.unix.domain.MassiveParallelTest.class); + // peer credential passing is AF_UNIX specific registerTest(org.newsclub.net.unix.domain.PeerCredentialsTest.class); - registerTest("junixsocket-common", PipeTest.class); + registerTest(COMMON, PipeTest.class); registerTest(org.newsclub.net.unix.domain.ReadWriteTest.class); @@ -84,6 +104,8 @@ public SelftestProvider() { registerTest(org.newsclub.net.unix.domain.SocketAddressTest.class); registerTest(org.newsclub.net.unix.domain.SocketChannelTest.class); + registerTestJavaInet(org.newsclub.net.unix.java.SocketChannelTest.class); + registerTestJEP380(org.newsclub.net.unix.jep380.SocketChannelTest.class); registerTest(org.newsclub.net.unix.domain.SocketFactoryTest.class); @@ -101,20 +123,27 @@ public SelftestProvider() { registerTest(org.newsclub.net.unix.domain.ThroughputTest.class); registerTestJavaInet(org.newsclub.net.unix.java.ThroughputTest.class); + + registerTest(org.newsclub.net.unix.domain.UnixDomainSocketAddressTest.class); } public Set modulesDisabledByDefault() { - return Collections.singleton("junixsocket-common.JavaInet"); + return new HashSet<>(Arrays.asList(COMMON_JAVA_INET, COMMON_JEP380)); } private void registerTest( // Class> testUnixDomain) { - registerTest("junixsocket-common", testUnixDomain); + registerTest(COMMON, testUnixDomain); } private void registerTestJavaInet( // Class> testJava) { - registerTest("junixsocket-common.JavaInet", testJava); + registerTest(COMMON_JAVA_INET, testJava); + } + + private void registerTestJEP380( // + Class> testJava) { + registerTest(COMMON_JEP380, testJava); } private void registerTest(String group, Class test) { @@ -126,9 +155,28 @@ private void registerTest(String group, Class test) { public Map[]> tests() { Map[]> tests = new LinkedHashMap<>(); testMap.forEach((key, set) -> { - tests.put(key, set.toArray(new Class[0])); + tests.put(key, set.toArray(new Class[0])); }); return tests; } + + public void printAdditionalProperties(PrintWriter out) { + out.println("Native architecture: " + NativeLibraryLoader.getArchitectureAndOS()); + out.println("Virtual threads support enabled: " + ThreadUtil.isVirtualThreadSupported()); + if (System.getProperty("org.newsclub.net.unix.virtual-threads") == null) { + if (ThreadUtil.isVirtualThreadSupported()) { + out.println(" ... disable with -Dorg.newsclub.net.unix.virtual-threads=false"); + } + } else { + if (!ThreadUtil.isVirtualThreadSupported()) { + out.println(" ... remove -Dorg.newsclub.net.unix.virtual-threads=false to re-enable"); + } + } + } + + public static void main(String[] args) { + new SelftestProvider().printAdditionalProperties(new PrintWriter(new OutputStreamWriter( + System.out, Charset.defaultCharset()), true)); + } } \ No newline at end of file diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/ServerSocketCloseTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/ServerSocketCloseTest.java index d5b48bf7d..8135e4e35 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/ServerSocketCloseTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/ServerSocketCloseTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,6 +59,7 @@ public void testUnblockAcceptsWithoutSoTimeout() throws Exception { testUnblockAccepts(0); } + @SuppressWarnings("PMD.CognitiveComplexity") private void testUnblockAccepts(int timeout) throws Exception { assertTimeoutPreemptively(Duration.ofSeconds(30), () -> { try (ServerSocket serverSocket = newServerSocketBindOn(getServerBindAddress())) { @@ -68,8 +69,10 @@ private void testUnblockAccepts(int timeout) throws Exception { final CountDownLatch cdl = new CountDownLatch(numThreads); + @SuppressWarnings("all") // "resource"; only since Java 19 ThreadPoolExecutor is + // AutoCloseable ThreadPoolExecutor threadPool = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, - TimeUnit.SECONDS, new SynchronousQueue()); + TimeUnit.SECONDS, new SynchronousQueue<>()); for (int i = 0; i < numThreads; i++) { threadPool.submit(new Runnable() { @@ -108,6 +111,17 @@ public void run() { if (active == numThreads) { checkFailedTestActuallySupported(); } + + int attemptsLeft = 50; + while (active > 0 && attemptsLeft-- > 0) { + Thread.sleep(100); + active = threadPool.getActiveCount(); + } + + if (active > 0) { + checkFailedTestActuallySupported(); + } + assertEquals(0, active, "There should be no pending accepts"); } }); diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/ServerSocketTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/ServerSocketTest.java index 66ec18760..eab79ff03 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/ServerSocketTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/ServerSocketTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ /** * Tests some otherwise uncovered methods of {@link AFSocket}. - * + * * @author Christian Kohlschütter */ @SuppressFBWarnings({ @@ -75,14 +75,17 @@ public void close() throws IOException { assertTrue(closed.get()); } + @SuppressWarnings("AddressSelection" /* errorprone */) @Test public void testBindBadArguments() throws Exception { try (ServerSocket sock = newServerSocket()) { assertFalse(sock.isBound()); - assertThrows(IllegalArgumentException.class, () -> { + + try { sock.bind(null); - }); - assertFalse(sock.isBound()); + } catch (UnsupportedOperationException e) { + assertFalse(sock.isBound()); + } } try (ServerSocket sock = newServerSocket()) { assertFalse(sock.isBound()); diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/SoTimeoutTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/SoTimeoutTest.java index 9f8b0b1b4..3706dec10 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/SoTimeoutTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/SoTimeoutTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,14 +32,15 @@ import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; -import org.opentest4j.TestAbortedException; import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; import com.kohlschutter.testutil.AssertUtil; +import com.kohlschutter.testutil.TestAbortedWithImportantMessageException; +import com.kohlschutter.testutil.TestAbortedWithImportantMessageException.MessageType; /** * Tests {@link Socket#setSoTimeout(int)} behavior. - * + * * @see Issue 14 */ @SuppressFBWarnings({ @@ -52,7 +53,7 @@ protected SoTimeoutTest(AddressSpecifics asp) { /** * Triggers a case where {@link Socket#setSoTimeout(int)} fails on some platforms: when the socket * is closed. - * + * * @throws IOException on error. */ @Test @@ -82,7 +83,7 @@ protected void handleConnection(final Socket socket) throws IOException { /** * Triggers a regular case where {@link Socket#setSoTimeout(int)} should work. - * + * * @throws IOException on error. */ @Test @@ -135,7 +136,20 @@ public void testSocketTimeoutExceptionWrite() throws Exception { Socket socket = pair.getSecond(); socket.setSoTimeout(500); if (socket.getSoTimeout() == 0) { - throw new TestAbortedException("Could not set socket timeout"); + boolean isZOS = "z/OS".equals(System.getProperty("os.name", "")); + + String afs = addressFamilyString(); + + if (isZOS && "AF_UNIX".equals(afs)) { + // https://www.ibm.com/docs/en/zos/2.5.0?topic=csd-getsockopt-setsockopt-bpx1opt-bpx4opt-get-set-options-associated-socket + throw new TestAbortedWithImportantMessageException(MessageType.TEST_ABORTED_WITH_ISSUES, + "Could not set socket timeout via Socket.setSoTimeout (" + addressFamilyString() + + "); this is a known limitation of z/OS"); + } else { + throw new TestAbortedWithImportantMessageException(MessageType.TEST_ABORTED_WITH_ISSUES, + "Could not set socket timeout via Socket.setSoTimeout (" + addressFamilyString() + + ")"); + } } byte[] buf = new byte[socket.getSendBufferSize()]; OutputStream out = socket.getOutputStream(); diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketChannelTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketChannelTest.java index fb4dea1d4..6e238f82e 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketChannelTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketChannelTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,18 +17,28 @@ */ package org.newsclub.net.unix; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; +import java.net.ServerSocket; import java.net.SocketAddress; import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.net.StandardSocketOptions; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.NotYetBoundException; +import java.nio.channels.NotYetConnectedException; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Objects; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; @@ -37,6 +47,7 @@ import com.kohlschutter.testutil.TestAbortedWithImportantMessageException; import com.kohlschutter.testutil.TestAbortedWithImportantMessageException.MessageType; +import com.kohlschutter.testutil.TestAsyncUtil; public abstract class SocketChannelTest extends SocketTestBase { protected SocketChannelTest(AddressSpecifics asp) { @@ -55,7 +66,7 @@ public void testNonBlockingConnect() throws IOException { { SocketChannel sc = selectorProvider().openSocketChannel(); sc.configureBlocking(false); - if (!sc.connect(sa)) { + if (!handleConnect(sc, sa)) { // connect pending assertTrue(sc.isConnected() || sc.isConnectionPending()); long now = System.currentTimeMillis(); @@ -90,13 +101,15 @@ public void testDoubleBindAddressReusable() throws Exception { testDoubleBind(true); } - @SuppressWarnings({"PMD.ExcessiveMethodLength", "PMD.CognitiveComplexity", "PMD.NPathComplexity"}) + @SuppressWarnings({ + "PMD.ExcessiveMethodLength", "PMD.CognitiveComplexity", "PMD.NPathComplexity", + "PMD.CyclomaticComplexity"}) private void testDoubleBind(boolean reuseAddress) throws Exception { SocketAddress sa0 = newTempAddress(); - final CompletableFuture acceptCall; - final CompletableFuture acceptCall2; - CompletableFuture connectCall = null; + final Future acceptCall; + final Future acceptCall2; + Future connectCall = null; AtomicBoolean socketDomainWillAcceptCallOnFirstBind = new AtomicBoolean(true); @@ -105,37 +118,66 @@ private void testDoubleBind(boolean reuseAddress) throws Exception { final SocketAddress sa = resolveAddressForSecondBind(sa0, ssc1); AtomicBoolean connectMustSucceed = new AtomicBoolean(false); + AtomicBoolean wasRebound = new AtomicBoolean(false); - acceptCall = CompletableFuture.supplyAsync(() -> { + acceptCall = TestAsyncUtil.supplyAsync(() -> { try { - SocketChannel sc = ssc1.accept(); + SocketChannel sc; + try { + sc = ssc1.accept(); + } catch (ClosedChannelException | SocketClosedException e) { + if (wasRebound.get()) { + // The system terminated our accept because another socket was rebound + // This may not occur on all systems, but we have to handle it. + return null; + } else { + throw e; + } + } socketDomainWillAcceptCallOnFirstBind.set(false); Objects.requireNonNull(sc); if (reuseAddress && !connectMustSucceed.get()) { - fail("Did not throw SocketException"); + // fail("Did not throw SocketException"); // no longer thrown in Sonoma 14.2.1? } return sc; - } catch (SocketException e) { + } catch (ClosedChannelException | SocketException e) { // NOPMD.ExceptionAsFlowControl + String msg = checkKnownBugAcceptFailure(e); + if (msg != null) { + throw new TestAbortedWithImportantMessageException( + MessageType.TEST_ABORTED_SHORT_WITH_ISSUES, msg, summaryImportantMessage(msg), e); + } if (reuseAddress) { // expected (Software caused connection abort) } else { fail(e); } - } catch (IOException e) { + } catch (SocketTimeoutException e) { + String msg = checkKnownBugAcceptFailure(e); + if (msg != null) { + throw new TestAbortedWithImportantMessageException( + MessageType.TEST_ABORTED_SHORT_WITH_ISSUES, msg, summaryImportantMessage(msg), e); + } + fail(e); + } catch (IOException e) { // NOPMD.ExceptionAsFlowControl fail(e); } return null; }); try (ServerSocketChannel ssc2 = selectorProvider().openServerSocketChannel()) { - ssc2.socket().setReuseAddress(reuseAddress); + try { + ssc2.setOption(StandardSocketOptions.SO_REUSEADDR, reuseAddress); + } catch (UnsupportedOperationException e) { + // ignore + } try { + wasRebound.set(true); bindServerSocket(ssc2, sa, 1); if (!reuseAddress && !socketDomainPermitsDoubleBind()) { fail("Did not throw expected SocketException (Address already in use)"); } - } catch (SocketException e) { + } catch (ClosedChannelException | SocketException e) { if (!reuseAddress) { // expected } else { @@ -146,7 +188,7 @@ private void testDoubleBind(boolean reuseAddress) throws Exception { } if (reuseAddress) { - acceptCall2 = CompletableFuture.supplyAsync(() -> { + acceptCall2 = TestAsyncUtil.supplyAsync(() -> { try { SocketChannel sc = ssc2.accept(); socketDomainWillAcceptCallOnFirstBind.set(false); @@ -171,9 +213,11 @@ private void testDoubleBind(boolean reuseAddress) throws Exception { // unblock accept of any successful bind if (!acceptCall.isDone() && socketDomainWillAcceptCallOnFirstBind.get()) { - connectCall = CompletableFuture.runAsync(() -> { + connectCall = TestAsyncUtil.supplyAsync(() -> { try { newSocket().connect(sa); + } catch (ClosedChannelException | SocketClosedException e) { + // ignore } catch (SocketException e) { if (connectMustSucceed.get()) { fail("Connect should have succeeded", e); @@ -205,12 +249,22 @@ private void testDoubleBind(boolean reuseAddress) throws Exception { acceptCall.get(5, TimeUnit.SECONDS); } catch (ExecutionException e) { // ignore socket closed etc. + if (e.getCause() instanceof TestAbortedWithImportantMessageException) { + throw (TestAbortedWithImportantMessageException) e.getCause(); // NOPMD.PreserveStackTrace + } else { + throw e; + } } catch (TimeoutException e) { triggerWithIssues = checkKnownBugFirstAcceptCallNotTerminated(); if (triggerWithIssues == null) { fail("First accept call did not terminate"); } } + if (triggerWithIssues != null) { + throw new TestAbortedWithImportantMessageException(MessageType.TEST_ABORTED_SHORT_WITH_ISSUES, + triggerWithIssues, summaryImportantMessage(triggerWithIssues)); + } + if (connectCall != null) { try { connectCall.get(5, TimeUnit.SECONDS); @@ -220,17 +274,116 @@ private void testDoubleBind(boolean reuseAddress) throws Exception { fail("Connect call did not terminate"); } } + } - if (triggerWithIssues != null) { - throw new TestAbortedWithImportantMessageException(MessageType.TEST_ABORTED_WITH_ISSUES, - triggerWithIssues); + /** + * Subclasses may override this to tell that there is a known issue with "accept". + * + * @param e The exception + * @return An explanation iff this should not cause a test failure but trigger "With issues". + */ + protected String checkKnownBugAcceptFailure(IOException e) { + return null; + } + + /** + * Bind the given address to the given {@link ServerSocketChannel}. + * + * By default, this just calls `ssc.bind(sa)`, but you may handle some exceptions by overriding + * this method in a subclass. + * + * @param ssc The server socket channel. + * @param sa The socket address to bind to. + * @throws IOException on error. + */ + protected void handleBind(ServerSocketChannel ssc, SocketAddress sa) throws IOException { + ssc.bind(sa); + } + + /** + * Connect the given {@link SocketChannel} with the given address. + * + * By default, this just calls `sc.connect(sa)`, but you may handle some exceptions by overriding + * this method in a subclass. + * + * @param sc The socket channel. + * @param sa The socket address to connect to. + * @throws IOException on error. + */ + protected boolean handleConnect(SocketChannel sc, SocketAddress sa) throws IOException { + return sc.connect(sa); + } + + @Test + public void testByteBufferWithPositionOffset() throws Exception { + SocketAddress sa = newTempAddress(); + + final int bb1Offset = 32; + final int bb2Offset = 1; + + byte[] data = new byte[96]; + getRandom().nextBytes(data); + + try (ServerSocketChannel ssc = selectorProvider().openServerSocketChannel()) { + handleBind(ssc, sa); + + ByteBuffer bb1 = ByteBuffer.allocate(data.length + bb1Offset); + bb1.position(bb1Offset); + + bb1.put(data); + bb1.flip(); + + bb1.position(bb1Offset); + assertEquals(bb1Offset, bb1.position()); + + TestAsyncUtil.runAsync(() -> { + try (SocketChannel sc = ssc.accept()) { + int written = 0; + while (bb1.hasRemaining()) { + written += sc.write(bb1); + } + assertEquals(data.length, written); + } catch (IOException e) { + fail(e); + } + }); + + SocketChannel sc = selectorProvider().openSocketChannel(); + + ByteBuffer bb2 = ByteBuffer.allocate(data.length + bb2Offset); + + assertTrue(handleConnect(sc, ssc.getLocalAddress())); + + bb2.position(bb2Offset); + + int read = 0; + int r; + do { + r = sc.read(bb2); + if (r == -1) { + break; + } + read += r; + } while (bb2.hasRemaining()); + assertEquals(data.length, read); + + assertEquals(bb1.capacity(), bb1.position()); + assertEquals(bb1.capacity(), bb1.limit()); + assertEquals(data.length + bb2Offset, bb2.position()); + assertEquals(bb2.capacity(), bb2.limit()); + + bb1.position(bb1Offset); + + for (int i = 0, n = data.length; i < n; i++) { + assertEquals(bb1.get(bb1Offset + i), bb2.get(bb2Offset + i), "at pos " + i); + } } } /** * Subclasses may override this to tell that there is a known issue with "First accept call did * not terminate". - * + * * @return An explanation iff this should not cause a test failure but trigger "With issues". */ protected String checkKnownBugFirstAcceptCallNotTerminated() { @@ -239,9 +392,9 @@ protected String checkKnownBugFirstAcceptCallNotTerminated() { /** * Returns the temporary address usable to binding on for a second bind. - * + * * Depending on the socket domain, a wildcard address may be permittable or not for a second bind. - * + * * @param originalAddress The original temporary address (e.g., a wildcard address). * @param ssc The socket that was bound to that address. * @return The local bound address, or the {@code originalAddress}. @@ -256,11 +409,74 @@ protected SocketAddress resolveAddressForSecondBind(SocketAddress originalAddres /** * Override to declare that a certain socket domain permits double-binding an address, * particularly when the address is comparable to a wildcard address. - * + * * @return {@code true} iff double-binding the same address is allowed. * @see #testDoubleBindAddressReusable() */ protected boolean socketDomainPermitsDoubleBind() { return false; } + + @Test + public void testReadNotConnectedYet() throws Exception { + SocketChannel sc = newSocketChannel(); + assertThrows(NotYetConnectedException.class, () -> sc.read(ByteBuffer.allocate(1))); + } + + @Test + public void testWriteNotConnectedYet() throws Exception { + SocketChannel sc = newSocketChannel(); + assertThrows(NotYetConnectedException.class, () -> sc.write(ByteBuffer.allocate(1))); + } + + @Test + public void testAcceptNotBoundYet() throws Exception { + ServerSocketChannel sc = newServerSocketChannel(); + assertThrows(NotYetBoundException.class, sc::accept); + } + + protected boolean mayTestBindNullThrowUnsupportedOperationException() { + return true; + } + + protected boolean mayTestBindNullHaveNullLocalSocketAddress() { + return true; + } + + protected void cleanupTestBindNull(ServerSocketChannel sc, SocketAddress addr) throws Exception { + } + + protected ServerSocket socketIfPossible(ServerSocketChannel channel) { + try { + return channel.socket(); + } catch (UnsupportedOperationException e) { + return null; + } + } + + @Test + public void testBindNull() throws Exception { + try (ServerSocketChannel sc = newServerSocketChannel()) { + ServerSocket s = socketIfPossible(sc); + assertTrue(s == null || !s.isBound()); + try { + sc.bind(null); + } catch (UnsupportedOperationException e) { + if (mayTestBindNullThrowUnsupportedOperationException()) { + // OK + return; + } else { + throw e; + } + } + assertTrue(s == null || s.isBound()); + + SocketAddress addr = sc.getLocalAddress(); + if (!mayTestBindNullHaveNullLocalSocketAddress()) { + assertNotNull(addr); + } + + cleanupTestBindNull(sc, addr); + } + } } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketOptionsTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketOptionsTest.java index 43432f20f..96f313bb5 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketOptionsTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketOptionsTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketPairTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketPairTest.java index d798c6006..f5f9d0d44 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketPairTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketPairTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -30,6 +29,7 @@ import java.util.Objects; import org.junit.jupiter.api.Test; +import org.opentest4j.AssertionFailedError; import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; @@ -87,14 +87,25 @@ public void testSocketPair() throws Exception { bb2.flip(); assertEquals(0x04030201, bb2.getInt()); + // Haiku OS uses "0x00 AA BB CC DD EE" as socket addresses for internal socket IDs + if (getServerBindAddress() instanceof AFUNIXSocketAddress) { - assertNull(pair.getFirst().getLocalAddress()); - assertNull(pair.getSecond().getLocalAddress()); - assertNull(pair.getFirst().getRemoteAddress()); - assertNull(pair.getSecond().getRemoteAddress()); - } else { + if (pair.getFirst().getLocalAddress() == null && pair.getSecond().getLocalAddress() == null + && pair.getFirst().getRemoteAddress() == null && pair.getSecond() + .getRemoteAddress() == null) { + // on many systems, socketpair with AF_UNIX returns all-null addresses + return; + } + } + try { assertCovered(pair.getFirst().getLocalAddress(), pair.getSecond().getRemoteAddress()); assertCovered(pair.getSecond().getLocalAddress(), pair.getFirst().getRemoteAddress()); + } catch (AssertionFailedError e) { + if (TestUtil.isHaikuOS()) { + throw TestUtil.haikuBug18534(e); + } else { + throw e; + } } } @@ -130,14 +141,25 @@ public void testDatagramPair() throws Exception { bb2.flip(); assertEquals(0x04030201, bb2.getInt()); + // NOTE: Haiku OS uses "0x00 AA BB CC DD EE" as socket addresses for internal socket IDs + if (getServerBindAddress() instanceof AFUNIXSocketAddress) { - assertNull(pair.getFirst().getLocalAddress()); - assertNull(pair.getSecond().getLocalAddress()); - assertNull(pair.getFirst().getRemoteAddress()); - assertNull(pair.getSecond().getRemoteAddress()); - } else { + if (pair.getFirst().getLocalAddress() == null && pair.getSecond().getLocalAddress() == null + && pair.getFirst().getRemoteAddress() == null && pair.getSecond() + .getRemoteAddress() == null) { + // on many systems, socketpair with AF_UNIX returns all-null addresses + return; + } + } + try { assertCovered(pair.getFirst().getLocalAddress(), pair.getSecond().getRemoteAddress()); assertCovered(pair.getSecond().getLocalAddress(), pair.getFirst().getRemoteAddress()); + } catch (AssertionFailedError e) { + if (TestUtil.isHaikuOS()) { + throw TestUtil.haikuBug18534(e); + } else { + throw e; + } } } } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketTest.java index 2044a0d42..4f2e5e39c 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,7 @@ /** * Tests some otherwise uncovered methods of {@link AFSocket}. - * + * * @author Christian Kohlschütter */ @SuppressFBWarnings({ @@ -45,6 +45,7 @@ protected SocketTest(AddressSpecifics asp) { super(asp); } + @SuppressWarnings("AddressSelection" /* errorprone */) @Test public void testConnectBadArguments() throws Exception { try (Socket socket = newSocket()) { @@ -86,6 +87,7 @@ public void testConnectBadArguments() throws Exception { } } + @SuppressWarnings("AddressSelection" /* errorprone */) @Test public void testBindBadArguments() throws Exception { try (Socket sock = newSocket()) { diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketTestBase.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketTestBase.java index 3fbf715fe..0d74ed438 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketTestBase.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketTestBase.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ import java.util.Random; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -45,23 +46,24 @@ import org.newsclub.net.unix.java.JavaAddressSpecifics; import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; +import com.kohlschutter.testutil.TestAbortedWithImportantMessageException; /** * Some base functionality for socket tests. - * + * * This class provides access to the {@link AddressSpecifics} methods for the socket implementation * under test. It is essential to use these wrapper methods in tests instead of directly calling the * {@link AFSocket} etc. methods: Some socket implementations (and sometimes only in certain * kernel/environment configurations) may expose unexpected behavior that is otherwise hard to * catch. - * + * * This is especially relevant when connecting/binding sockets (see * {@link #connectSocket(Socket, SocketAddress)}, #bindServerSocket(ServerSocket, SocketAddress)}, * etc.) - * + * * @author Christian Kohlschuetter */ -@SuppressWarnings("PMD.AbstractClassWithoutAbstractMethod") +@SuppressWarnings({"PMD.AbstractClassWithoutAbstractMethod", "PMD.CouplingBetweenObjects"}) @SuppressFBWarnings({ "THROWS_METHOD_THROWS_CLAUSE_THROWABLE", "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION"}) public abstract class SocketTestBase { // NOTE: needs to be public for @@ -86,6 +88,11 @@ protected SocketTestBase(AddressSpecifics asp) { } } + @SuppressWarnings("all") + @Deprecated + protected final void finalize() { + } + private static File initSocketFile() { return SocketTestBase.newTempFile(System.getProperty("org.newsclub.net.unix.testsocket")); } @@ -94,7 +101,7 @@ public static File socketFile() { return SOCKET_FILE; } - protected final SocketAddress newTempAddress() throws IOException { + protected SocketAddress newTempAddress() throws IOException { return asp.newTempAddress(); } @@ -145,9 +152,9 @@ protected ServerSocket startServer() throws IOException { /** * Checks if an optional connection check via {@link AFSocket#checkConnectionClosed()}, is to be * run upon {@link AFServerSocket#accept()}. - * + * * Override to enable. - * + * * @return {@code true} if enabled; default is {@code false} = disabled. */ protected boolean shouldDoConnectionCheckUponAccept() { @@ -164,14 +171,26 @@ protected abstract class ServerThread extends Thread implements AutoCloseable { private volatile Error error = null; private final AtomicBoolean loop = new AtomicBoolean(true); private final Semaphore sema = new Semaphore(1); + private final Semaphore readySema = new Semaphore(0); @SuppressFBWarnings("SC_START_IN_CTOR") - protected ServerThread() throws IOException { + protected ServerThread() throws IOException, InterruptedException { super(); serverSocket = startServer(); // NOPMD setDaemon(true); start(); + readySema.acquire(); + } + + @SuppressWarnings("all") + @Deprecated + protected final void finalize() { + } + + @Override + public final void start() { + super.start(); } protected ServerSocket startServer() throws IOException { @@ -186,7 +205,7 @@ public void close() throws Exception { /** * Stops the server. - * + * * @throws IOException on error. */ public void shutdown() throws IOException { @@ -199,11 +218,11 @@ public void shutdown() throws IOException { /** * Callback used to handle a connection call. - * + * * After returning from this call, the socket is closed. - * + * * Use {@link #stopAcceptingConnections()} to stop accepting new calls. - * + * * @param sock The socket to handle. * @throws IOException upon error. */ @@ -212,9 +231,9 @@ public void shutdown() throws IOException { /** * Called from within {@link #handleConnection(Socket)} to tell the server to no longer accept * new calls and to terminate the server thread. - * + * * Note that this will lead to existing client connections to be closed. - * + * * If you want to deny new connections but finish your work on the client side (in another * thread), then please use semaphores etc. to ensure reaching a safe state before calling this * method. @@ -229,7 +248,7 @@ protected void onServerSocketClose() { /** * Returns the server socket. - * + * * @return the server socket. */ @SuppressFBWarnings("EI_EXPOSE_REP") @@ -239,7 +258,7 @@ public ServerSocket getServerSocket() { /** * Returns the server's address to connect to. - * + * * @return the address. */ public SocketAddress getServerAddress() { @@ -248,7 +267,7 @@ public SocketAddress getServerAddress() { /** * Called upon receiving an exception that may be handled specifically. - * + * * @param e The exception * @return {@link ExceptionHandlingDecision#RAISE} if we should handle the exception somehow, * {@link ExceptionHandlingDecision#IGNORE} if we should pretend the exception never @@ -279,7 +298,7 @@ protected void acceptAndHandleConnection() throws IOException { if (acceptSuccess) { handleConnection(sock); } - } catch (IOException e) { + } catch (IOException e) { // NOPMD.ExceptionAsFlowControl if (!acceptSuccess) { // ignore: connection closed before accept could complete if (serverSocket.isClosed()) { @@ -296,9 +315,11 @@ protected void acceptAndHandleConnection() throws IOException { } @Override + @SuppressWarnings("PMD.AvoidInstanceofChecksInCatchClause") public final void run() { try { loop.set(true); + readySema.release(); onServerReady(); while (loop.get()) { acceptAndHandleConnection(); @@ -307,8 +328,14 @@ public final void run() { if (!loop.get()) { // ignore } else if (handleException(e) != ExceptionHandlingDecision.IGNORE) { - e.addSuppressed(caller); - exception = e; + if (e instanceof TimeoutException + && caller instanceof TestAbortedWithImportantMessageException) { + caller.addSuppressed(e); + exception = caller; + } else { + e.addSuppressed(caller); + exception = e; + } } } catch (Error e) { error = e; @@ -323,9 +350,9 @@ protected void onServerReady() { /** * Checks if there were any exceptions thrown during the lifetime of this ServerThread. - * + * * NOTE: This call blocks until the Thread actually terminates. - * + * * @throws Exception upon error. */ public void checkException() throws Exception { @@ -343,7 +370,7 @@ public void checkException() throws Exception { } protected abstract class AFUNIXServerThread extends ServerThread { - protected AFUNIXServerThread() throws IOException { + protected AFUNIXServerThread() throws IOException, InterruptedException { super(); } @@ -357,7 +384,7 @@ protected final void handleConnection(Socket sock) throws IOException { /** * Sleeps for the given amount of milliseconds. - * + * * @param ms The duration in milliseconds. * @throws InterruptedIOException when interrupted. */ @@ -378,6 +405,16 @@ protected final Socket newSocket() throws IOException { return asp.newSocket(); } + protected final Socket newConnectedSocket(SocketAddress addr) throws IOException { + Socket s = asp.newSocket(); + s.connect(addr); + return s; + } + + protected final SocketChannel newSocketChannel() throws IOException { + return asp.newSocketChannel(); + } + protected final Socket newStrictSocket() throws IOException { return asp.newStrictSocket(); } @@ -394,6 +431,17 @@ protected final ServerSocket newServerSocket() throws IOException { return asp.newServerSocket(); } + protected ServerSocketChannel newServerSocketChannel() throws IOException { + return asp.newServerSocketChannel(); + } + + protected ServerSocketChannel newServerSocketChannelBindOn(SocketAddress addr) + throws IOException { + ServerSocketChannel ssc = asp.newServerSocketChannel(); + ssc.bind(addr); + return ssc; + } + protected final ServerSocket newServerSocketBindOn(SocketAddress addr) throws IOException { return asp.newServerSocketBindOn(addr); } @@ -448,13 +496,32 @@ protected final void connectSocket(Socket socket, SocketAddress endpoint) throws protected final boolean connectSocket(SocketChannel socketChannel, SocketAddress endpoint) throws IOException { - return asp.connectSocket(socketChannel, endpoint); + try { + return asp.connectSocket(socketChannel, endpoint); + } catch (IllegalStateException | IOException e) { + throw handleConnectSocketException(socketChannel, endpoint, e); + } + } + + protected IOException handleConnectSocketException(SocketChannel socketChannel, + SocketAddress endpoint, Exception e) { + if (e instanceof IOException) { + return (IOException)e; + } else if (e instanceof RuntimeException) { + throw (RuntimeException)e; + } else { + throw new IllegalStateException(e); + } } protected CloseablePair newInterconnectedSockets() throws IOException { return asp.newInterconnectedSockets(); } + protected String addressFamilyString() { + return asp.addressFamilyString(); + } + protected Random getRandom() { return RANDOM; } @@ -463,7 +530,7 @@ protected Random getRandom() { * Returns the Linux kernel's major and minor version as an integer array (i.e., {@code 5.10.2 -> * int[]{5,10}}), or {@code null} if the running system isn't Linux or the version could not be * determined. - * + * * @return The running Linux kernels' major and minor version as an integer array, or * {@code null}. */ @@ -478,4 +545,15 @@ protected int[] getLinuxMajorMinorVersion() { return null; // NOPMD.PMD.ReturnEmptyCollectionRatherThanNull } } + + /** + * Provide a potentially different string for the "important message" section in selftest, given + * an existing error message. + * + * @param message The original message. + * @return A potentially different string. + */ + protected String summaryImportantMessage(String message) { + return asp.summaryImportantMessage(message); + } } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/StandardSocketOptionsTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/StandardSocketOptionsTest.java index 4ed2fc770..b638cf932 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/StandardSocketOptionsTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/StandardSocketOptionsTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,11 +47,12 @@ /** * Tests the {@code Socket#getOption(SocketOption)} API available since Java 9. - * + * * @author Christian Kohlschütter */ @SuppressFBWarnings({ "THROWS_METHOD_THROWS_CLAUSE_THROWABLE", "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION"}) +@SuppressWarnings("PMD.CouplingBetweenObjects") public abstract class StandardSocketOptionsTest extends SocketTestBase { private static final Set> IGNORABLE_OPTIONS = Set.of( // StandardSocketOptions.IP_TOS, // diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/StdinSocketApp.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/StdinSocketApp.java index 060e258ab..137c009a8 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/StdinSocketApp.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/StdinSocketApp.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ /** * This test app responds with "Hello world" over a {@link Socket} that was passed from another * process via standard input. - * + * * @author Christian Kohlschütter * @see org.newsclub.net.unix.domain.FileDescriptorCastTest#testForkedVMRedirectStdin() */ diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/TcpNoDelayTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/TcpNoDelayTest.java index d11dcfc5e..c42c12767 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/TcpNoDelayTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/TcpNoDelayTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/TestUtil.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/TestUtil.java new file mode 100644 index 000000000..3bce868db --- /dev/null +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/TestUtil.java @@ -0,0 +1,100 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix; + +import com.kohlschutter.testutil.TestAbortedWithImportantMessageException; +import com.kohlschutter.testutil.TestAbortedWithImportantMessageException.MessageType; + +/** + * Some test-related checks. + * + * @author Christian Kohlschütter + */ +public final class TestUtil { + private static final boolean IS_HAIKU_OS = "Haiku".equals(System.getProperty("os.name")); + + private TestUtil() { + throw new IllegalStateException("No instances"); + } + + /** + * Checks if the system under test is Haiku Os. + * + * @return {@code true} if so. + */ + public static boolean isHaikuOS() { + // Checks if the system + // under test is Haiku Os. + // Return true if so. + return IS_HAIKU_OS; + } + + private static void handleBug(String id, Throwable e) throws Exception { + String key = "selftest." + id; + switch (System.getProperty(key, "")) { + case "dump": + e.printStackTrace(); + break; + case "fail": + if (e instanceof Exception) { + throw (Exception) e; + } else if (e instanceof Error) { + throw (Error) e; + } else { + throw new IllegalStateException(e); + } + case "": + // nothing + return; + default: + System.err.println("Invalid value for System property " + key); + } + } + + /** + * In certain Haiku environments, socketpair/connect/accept for AF_UNIX is buggy; it may not set + * the "connected state" (fixed in hrev57189). + * + * See Haiku Bug 18534 for details. + * + * @param e The caught throwable. + * @return A TestAbortedWithImportantMessageException wrapping the caught throwable. + */ + public static Exception haikuBug18534(Throwable e) throws Exception { + handleBug("haikuBug18534", e); + return new TestAbortedWithImportantMessageException(MessageType.TEST_ABORTED_SHORT_WITH_ISSUES, + "AF_UNIX support is buggy in this Haiku release; see https://dev.haiku-os.org/ticket/18534", + e); + } + + /** + * In certain Haiku environments, working with datagram sockets may result in a kernel hang + * (spinlock upon send). + * + * See Haiku Bug 18535 for details. + * + * @param e The caught throwable. + * @return A TestAbortedWithImportantMessageException wrapping the caught throwable. + */ + public static Exception haikuBug18535(Throwable e) throws Exception, Error { + handleBug("haikuBug18535", e); + return new TestAbortedWithImportantMessageException(MessageType.TEST_ABORTED_SHORT_WITH_ISSUES, + "AF_UNIX datagram support is buggy in this Haiku release or environment; see https://dev.haiku-os.org/ticket/18535", + e); + } +} diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/ThroughputTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/ThroughputTest.java index 386068f96..eff3e7eac 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/ThroughputTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/ThroughputTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,7 +46,6 @@ import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; @@ -55,20 +54,27 @@ import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; +import org.junit.platform.commons.JUnitException; +import org.opentest4j.AssertionFailedError; import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; +import com.kohlschutter.testutil.TestAsyncUtil; +import com.kohlschutter.testutil.TestStackTraceUtil; import com.kohlschutter.util.SystemPropertyUtil; /** * This test measures throughput for sending and receiving messages over AF_UNIX, comparing * implementations of junixsocket and JEP 380 (Java 16). * - * The test is enabled by default, and also included with the self-test. + * The test is enabled by default (only runs for a very short time), and is also included within the + * self-test. * * The tests can be configured as follows (all system properties): *

    *
  • org.newsclub.net.unix.throughput-test.enabled (0/1, default: 1)
  • *
  • org.newsclub.net.unix.throughput-test.payload-size (bytes, e.g., 8192)
  • + *
  • org.newsclub.net.unix.throughput-test.payload-size.datagram (bytes, e.g., 2048; + * defaults to value specified with "payload-size" above)
  • *
  • org.newsclub.net.unix.throughput-test.seconds (default: 0)
  • *
* @@ -82,8 +88,12 @@ public abstract class ThroughputTest extends SocketTest "org.newsclub.net.unix.throughput-test.enabled", 1); protected static final int PAYLOAD_SIZE = SystemPropertyUtil.getIntSystemProperty( "org.newsclub.net.unix.throughput-test.payload-size", 2048); // 8192 is much faster + protected static final int PAYLOAD_SIZE_DATAGRAM = SystemPropertyUtil.getIntSystemProperty( + "org.newsclub.net.unix.throughput-test.payload-size.datagram", PAYLOAD_SIZE); protected static final int NUM_SECONDS = SystemPropertyUtil.getIntSystemProperty( "org.newsclub.net.unix.throughput-test.seconds", 0); + protected static final int GRACE_TIME_NUM_SECONDS = SystemPropertyUtil.getIntSystemProperty( + "org.newsclub.net.unix.throughput-test.gracetime.seconds", 5); protected static final int NUM_MILLISECONDS = Math.max(50, NUM_SECONDS * 1000); protected ThroughputTest(AddressSpecifics asp) { @@ -111,7 +121,7 @@ public void testSocket() throws Exception { assumeTrue(ENABLED > 0, "Throughput tests are disabled"); assumeTrue(PAYLOAD_SIZE > 0, "Payload must be positive"); - assertTimeoutPreemptively(Duration.ofSeconds(NUM_SECONDS + 5), () -> { + assertTimeoutPreemptively(Duration.ofSeconds(NUM_SECONDS + GRACE_TIME_NUM_SECONDS), () -> { try (ServerThread serverThread = new ServerThread() { @Override protected void handleConnection(final Socket sock) throws IOException { @@ -128,9 +138,10 @@ protected void handleConnection(final Socket sock) throws IOException { }) { AtomicBoolean keepRunning = new AtomicBoolean(true); - Executors.newSingleThreadScheduledExecutor().schedule(() -> { + + TestAsyncUtil.runAsyncDelayed(NUM_MILLISECONDS, TimeUnit.MILLISECONDS, () -> { keepRunning.set(false); - }, NUM_MILLISECONDS, TimeUnit.MILLISECONDS); + }); try (Socket sock = connectTo(serverThread.getServerAddress())) { byte[] buf = createTestData(PAYLOAD_SIZE); @@ -177,7 +188,7 @@ protected void handleConnection(final Socket sock) throws IOException { public void testSocketChannel() throws Exception { assumeTrue(ENABLED > 0, "Throughput tests are disabled"); assumeTrue(PAYLOAD_SIZE > 0, "Payload must be positive"); - assertTimeoutPreemptively(Duration.ofSeconds(NUM_SECONDS + 5), () -> { + assertTimeoutPreemptively(Duration.ofSeconds(NUM_SECONDS + GRACE_TIME_NUM_SECONDS), () -> { runtestSocketChannel(false); }); } @@ -186,7 +197,7 @@ public void testSocketChannel() throws Exception { public void testSocketChannelDirectBuffer() throws Exception { assumeTrue(ENABLED > 0, "Throughput tests are disabled"); assumeTrue(PAYLOAD_SIZE > 0, "Payload must be positive"); - assertTimeoutPreemptively(Duration.ofSeconds(NUM_SECONDS + 5), () -> { + assertTimeoutPreemptively(Duration.ofSeconds(NUM_SECONDS + GRACE_TIME_NUM_SECONDS), () -> { runtestSocketChannel(true); }); } @@ -216,7 +227,7 @@ protected void runTestSocketChannel(String implId, SocketAddress sba, ServerSock SupplierWithException sscSupp, boolean direct) throws Exception { final AtomicBoolean keepRunning = new AtomicBoolean(true); - try (ServerThread serverThread = new ServerThread() { + try (ServerThread unused = new ServerThread() { @Override protected ServerSocket startServer() throws IOException { @@ -248,7 +259,7 @@ protected void acceptAndHandleConnection() throws IOException { sc.write(bb); bb.clear(); } - } catch (SocketException | SocketTimeoutException e) { + } catch (SocketException | ClosedChannelException | SocketTimeoutException e) { if (keepRunning.get()) { throw e; } else { @@ -264,9 +275,9 @@ protected void handleConnection(Socket sock) throws IOException { } }) { - Executors.newSingleThreadScheduledExecutor().schedule(() -> { + TestAsyncUtil.runAsyncDelayed(NUM_MILLISECONDS, TimeUnit.MILLISECONDS, () -> { keepRunning.set(false); - }, NUM_MILLISECONDS, TimeUnit.MILLISECONDS); + }); try (SocketChannel sc = sscSupp.get()) { ByteBuffer bb = direct ? ByteBuffer.allocateDirect(PAYLOAD_SIZE) : ByteBuffer.allocate( @@ -278,7 +289,7 @@ protected void handleConnection(Socket sock) throws IOException { bb.clear(); bb.put(createTestData(PAYLOAD_SIZE)); bb.flip(); - int remaining = sc.write(bb); + long remaining = sc.write(bb); bb.clear(); long read; // limited by net.local.stream.recvspace / sendspace etc. @@ -301,123 +312,162 @@ protected void handleConnection(Socket sock) throws IOException { @Test @SuppressWarnings("PMD.CognitiveComplexity") public void testDatagramPacket() throws Exception { - assertTimeoutPreemptively(Duration.ofSeconds(NUM_SECONDS + 5), () -> { - SocketAddress dsAddr = newTempAddressForDatagram(); - SocketAddress dcAddr = newTempAddressForDatagram(); + try { + assertTimeoutPreemptively(Duration.ofSeconds(NUM_SECONDS + GRACE_TIME_NUM_SECONDS), () -> { + SocketAddress dsAddr = newTempAddressForDatagram(); + SocketAddress dcAddr = newTempAddressForDatagram(); - try (DatagramSocket ds = newDatagramSocket(); DatagramSocket dc = newDatagramSocket()) { - if (!ds.isBound()) { - ds.bind(dsAddr); - } - if (!dc.isBound()) { - dc.bind(dcAddr); - } + try (DatagramSocket ds = newDatagramSocket(); DatagramSocket dc = newDatagramSocket()) { + if (!ds.isBound()) { + ds.bind(dsAddr); + } + if (!dc.isBound()) { + dc.bind(dcAddr); + } - dsAddr = ds.getLocalSocketAddress(); - dcAddr = dc.getLocalSocketAddress(); + dsAddr = ds.getLocalSocketAddress(); + dcAddr = dc.getLocalSocketAddress(); - assertNotEquals(dsAddr, dcAddr); + assertNotEquals(dsAddr, dcAddr); - dc.connect(dsAddr); + dc.connect(dsAddr); - AtomicBoolean keepRunning = new AtomicBoolean(true); - Executors.newSingleThreadScheduledExecutor().schedule(() -> { - keepRunning.set(false); - }, NUM_MILLISECONDS, TimeUnit.MILLISECONDS); + AtomicBoolean keepRunning = new AtomicBoolean(true); + TestAsyncUtil.runAsyncDelayed(NUM_MILLISECONDS, TimeUnit.MILLISECONDS, () -> { + keepRunning.set(false); + }); - AtomicLong readTotal = new AtomicLong(); - long sentTotal = 0; + AtomicLong readTotal = new AtomicLong(); + long sentTotal = 0; - new Thread() { - final DatagramPacket dp = new DatagramPacket(new byte[PAYLOAD_SIZE], PAYLOAD_SIZE); + new Thread() { + final DatagramPacket dp = new DatagramPacket(new byte[PAYLOAD_SIZE_DATAGRAM], + PAYLOAD_SIZE_DATAGRAM); - @Override - public void run() { - try { - while (!Thread.interrupted() && !ds.isClosed()) { - try { - ds.receive(dp); - } catch (SocketTimeoutException e) { - continue; + @Override + public void run() { + try { + while (!Thread.interrupted() && !ds.isClosed()) { + try { + ds.receive(dp); + } catch (SocketTimeoutException e) { + continue; + } + int read = dp.getLength(); + if (read != PAYLOAD_SIZE_DATAGRAM && read != 0) { + throw new IOException("Unexpected response length: " + read); + } + readTotal.addAndGet(dp.getLength()); } - int read = dp.getLength(); - if (read != PAYLOAD_SIZE && read != 0) { - throw new IOException("Unexpected response length: " + read); + } catch (SocketException e) { + if (keepRunning.get()) { + TestStackTraceUtil.printStackTrace(e); } - readTotal.addAndGet(dp.getLength()); + } catch (IOException e) { // NOPMD.ExceptionAsFlowControl + TestStackTraceUtil.printStackTrace(e); } - } catch (SocketException e) { - if (keepRunning.get()) { - e.printStackTrace(); - } - } catch (IOException e) { - e.printStackTrace(); } - } - }.start(); + }.start(); - long time = System.currentTimeMillis(); + long time = System.currentTimeMillis(); - DatagramPacket dp = new DatagramPacket(new byte[PAYLOAD_SIZE], PAYLOAD_SIZE); - byte[] data = dp.getData(); - for (int i = 0; i < data.length; i++) { - data[i] = (byte) i; - } - - while (keepRunning.get()) { - try { - dc.send(dp); - } catch (PortUnreachableException e) { - e.addSuppressed(new Exception(dp.getSocketAddress().toString())); - throw e; + DatagramPacket dp = new DatagramPacket(new byte[PAYLOAD_SIZE_DATAGRAM], + PAYLOAD_SIZE_DATAGRAM); + byte[] data = dp.getData(); + for (int i = 0; i < data.length; i++) { + data[i] = (byte) i; } - sentTotal += PAYLOAD_SIZE; - } - time = System.currentTimeMillis() - time; - keepRunning.set(false); - ds.close(); // terminate server - long readTotal0 = readTotal.get(); + while (keepRunning.get()) { + try { + dc.send(dp); + } catch (PortUnreachableException e) { + e.addSuppressed(new Exception(dp.getSocketAddress().toString())); + throw e; + } + sentTotal += PAYLOAD_SIZE_DATAGRAM; + } + time = System.currentTimeMillis() - time; + keepRunning.set(false); + ds.close(); // terminate server - reportResults(stbTestType() + " DatagramPacket", ((1000f * readTotal0 / time) / 1000f - / 1000f) + " MB/s for payload size " + PAYLOAD_SIZE + "; " + String.format( - Locale.ENGLISH, "%.1f%% packet loss", 100 * (1 - (readTotal0 - / (float) sentTotal)))); - } - }); + long readTotal0 = readTotal.get(); + reportResults(stbTestType() + " DatagramPacket", ((1000f * readTotal0 / time) / 1000f + / 1000f) + " MB/s for datagram payload size " + PAYLOAD_SIZE_DATAGRAM + "; " + String + .format(Locale.ENGLISH, "%.1f%% packet loss", 100 * (1 - (readTotal0 + / (float) sentTotal)))); + } + }); + } catch (JUnitException e) { + // Ignore timeout failure (this is a throughput test only) + TestStackTraceUtil.printStackTrace(e); + } } @Test @AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DATAGRAMS) public void testDatagramChannel() throws Exception { - assertTimeoutPreemptively(Duration.ofSeconds(NUM_SECONDS + 5), () -> { - testDatagramChannel(false, true); - }); + try { + assertTimeoutPreemptively(Duration.ofSeconds(NUM_SECONDS + GRACE_TIME_NUM_SECONDS), () -> { + testDatagramChannel(false, true); + }); + } catch (AssertionFailedError e) { + if (TestUtil.isHaikuOS()) { + throw TestUtil.haikuBug18535(e); + } else { + throw e; + } + } catch (JUnitException e) { + // Ignore timeout failure (this is a throughput test only) + TestStackTraceUtil.printStackTrace(e); + } } @Test @AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DATAGRAMS) public void testDatagramChannelDirect() throws Exception { - assertTimeoutPreemptively(Duration.ofSeconds(NUM_SECONDS + 5), () -> { - testDatagramChannel(true, true); - }); + try { + assertTimeoutPreemptively(Duration.ofSeconds(NUM_SECONDS + GRACE_TIME_NUM_SECONDS), () -> { + testDatagramChannel(true, true); + }); + } catch (AssertionFailedError e) { + if (TestUtil.isHaikuOS()) { + throw TestUtil.haikuBug18535(e); + } else { + throw e; + } + } catch (JUnitException e) { + // Ignore timeout failure (this is a throughput test only) + TestStackTraceUtil.printStackTrace(e); + } } @Test @AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DATAGRAMS) public void testDatagramChannelNonBlocking() throws Exception { - assertTimeoutPreemptively(Duration.ofSeconds(NUM_SECONDS + 5), () -> { - testDatagramChannel(false, false); - }); + try { + assertTimeoutPreemptively(Duration.ofSeconds(NUM_SECONDS + GRACE_TIME_NUM_SECONDS), () -> { + testDatagramChannel(false, false); + }); + } catch (JUnitException | AssertionError e) { + // Ignore timeout failure (this is a throughput test only) + // Notably, NativeUnixSocket.close may hang on AIX and IBM i + TestStackTraceUtil.printStackTrace(e); + } } @Test @AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DATAGRAMS) public void testDatagramChannelNonBlockingDirect() throws Exception { - assertTimeoutPreemptively(Duration.ofSeconds(NUM_SECONDS + 5), () -> { - testDatagramChannel(true, false); - }); + try { + assertTimeoutPreemptively(Duration.ofSeconds(NUM_SECONDS + GRACE_TIME_NUM_SECONDS), () -> { + testDatagramChannel(true, false); + }); + } catch (JUnitException e) { + // Ignore timeout failure (this is a throughput test only) + TestStackTraceUtil.printStackTrace(e); + } } private void testDatagramChannel(boolean direct, boolean blocking) throws Exception { @@ -458,14 +508,20 @@ protected void testSocketDatagramChannel(String id, DatagramChannel ds, Datagram // ds.setOption(StandardSocketOptions.SO_RCVBUF, (PAYLOAD_SIZE + 82)); AtomicBoolean keepRunning = new AtomicBoolean(true); - Executors.newSingleThreadScheduledExecutor().schedule(() -> { + TestAsyncUtil.runAsyncDelayed(NUM_MILLISECONDS, TimeUnit.MILLISECONDS, () -> { keepRunning.set(false); + try { ds.close(); } catch (IOException e) { - // ignore + TestStackTraceUtil.printStackTrace(e); } - }, NUM_MILLISECONDS, TimeUnit.MILLISECONDS); + try { + dc.close(); + } catch (IOException e) { + TestStackTraceUtil.printStackTrace(e); + } + }); AtomicLong readTotal = new AtomicLong(); long sentTotal = 0; @@ -477,8 +533,8 @@ protected void testSocketDatagramChannel(String id, DatagramChannel ds, Datagram new Thread() { @Override public void run() { - final ByteBuffer receiveBuffer = direct ? ByteBuffer.allocateDirect(PAYLOAD_SIZE) - : ByteBuffer.allocate(PAYLOAD_SIZE); + final ByteBuffer receiveBuffer = direct ? ByteBuffer.allocateDirect(PAYLOAD_SIZE_DATAGRAM) + : ByteBuffer.allocate(PAYLOAD_SIZE_DATAGRAM); try { SelectionKey key; if (readSelector != null) { @@ -489,7 +545,7 @@ public void run() { while (!Thread.interrupted() && keepRunning.get() && !bytesRead.isCancelled()) { int read; if (readSelector != null) { - int numReady = readSelector.select(); + int numReady = readSelector.select(1000); if (numReady == 0) { continue; } @@ -503,7 +559,7 @@ public void run() { } read = ds.read(receiveBuffer); receiveBuffer.rewind(); - if (read != PAYLOAD_SIZE && read != 0 && read != -1) { + if (read != PAYLOAD_SIZE_DATAGRAM && read != 0 && read != -1) { throw new IOException("Unexpected response length: " + read); } readTotal.addAndGet(read); @@ -516,7 +572,7 @@ public void run() { } else { bytesRead.complete(readTotal.get()); } - } catch (Exception e) { + } catch (Exception e) { // NOPMD.ExceptionAsFlowControl keepRunning.set(false); bytesRead.completeExceptionally(e); } @@ -525,8 +581,8 @@ public void run() { time = System.currentTimeMillis(); - final ByteBuffer sendBuffer = direct ? ByteBuffer.allocateDirect(PAYLOAD_SIZE) : ByteBuffer - .allocate(PAYLOAD_SIZE); + final ByteBuffer sendBuffer = direct ? ByteBuffer.allocateDirect(PAYLOAD_SIZE_DATAGRAM) + : ByteBuffer.allocate(PAYLOAD_SIZE_DATAGRAM); try (AbstractSelector writeSelector = sp == null ? null : sp.openSelector()) { // NOPMD if (sp != null) { @@ -534,7 +590,7 @@ public void run() { } while (keepRunning.get()) { if (writeSelector != null) { - int numReady = writeSelector.select(); + int numReady = writeSelector.select(1000); if (numReady == 0) { continue; } @@ -543,7 +599,7 @@ public void run() { int written; try { written = dc.write(sendBuffer); - } catch (SocketException e) { + } catch (SocketException | ClosedChannelException | SocketTimeoutException e) { if (keepRunning.get()) { throw e; } else { @@ -551,11 +607,11 @@ public void run() { } } - if (written != PAYLOAD_SIZE && written != 0) { + if (written != PAYLOAD_SIZE_DATAGRAM && written != 0) { throw new IOException("Unexpected written length: " + written); } - sentTotal += PAYLOAD_SIZE; + sentTotal += PAYLOAD_SIZE_DATAGRAM; sendBuffer.rewind(); } } finally { @@ -566,16 +622,21 @@ public void run() { } try { - bytesRead.get(2, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - e.printStackTrace(); + bytesRead.get(NUM_MILLISECONDS, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + if (NUM_SECONDS != 0) { + TestStackTraceUtil.printStackTrace(e); + } + } catch (InterruptedException | ExecutionException e) { + TestStackTraceUtil.printStackTrace(e); } long readTotal0 = readTotal.get(); reportResults(id + " direct=" + direct + ";blocking=" + blocking, ((1000f * readTotal0 / time) - / 1000f / 1000f) + " MB/s for payload size " + PAYLOAD_SIZE + "; " + String.format( - Locale.ENGLISH, "%.1f%% packet loss", 100 * (1 - (readTotal0 / (float) sentTotal)))); + / 1000f / 1000f) + " MB/s for datagram payload size " + PAYLOAD_SIZE_DATAGRAM + "; " + + String.format(Locale.ENGLISH, "%.1f%% packet loss", 100 * (1 - (readTotal0 + / (float) sentTotal)))); } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/AFUNIXAddressSpecifics.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/AFUNIXAddressSpecifics.java index 29b1e9993..3b09463c9 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/AFUNIXAddressSpecifics.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/AFUNIXAddressSpecifics.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import java.net.SocketAddress; import java.net.SocketException; import java.nio.channels.DatagramChannel; +import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import org.newsclub.net.unix.AFDatagramSocket; @@ -33,8 +34,10 @@ import org.newsclub.net.unix.AFUNIXDatagramSocket; import org.newsclub.net.unix.AFUNIXSelectorProvider; import org.newsclub.net.unix.AFUNIXServerSocket; +import org.newsclub.net.unix.AFUNIXServerSocketChannel; import org.newsclub.net.unix.AFUNIXSocket; import org.newsclub.net.unix.AFUNIXSocketAddress; +import org.newsclub.net.unix.AFUNIXSocketChannel; import org.newsclub.net.unix.AFUNIXSocketPair; import org.newsclub.net.unix.AddressSpecifics; import org.newsclub.net.unix.CloseablePair; @@ -48,7 +51,7 @@ private AFUNIXAddressSpecifics() { @Override public AFSocketAddress newTempAddress() throws IOException { - return AFUNIXSocketAddress.of(SocketTestBase.socketFile()); + return AFUNIXSocketAddress.ofNewTempFile(); } @Override @@ -66,6 +69,11 @@ public AFDatagramSocket newDatagramSocket() throws IOException { return AFUNIXDatagramSocket.newInstance(); } + @Override + public SocketChannel newSocketChannel() throws IOException { + return AFUNIXSocketChannel.open(); + } + @Override public DatagramChannel newDatagramChannel() throws IOException { return AFUNIXDatagramSocket.newInstance().getChannel(); @@ -116,4 +124,19 @@ public AFServerSocket newServerSocketBindOn(SocketAddress addr, boolean delet public Socket connectTo(SocketAddress socket) throws IOException { return AFUNIXSocket.connectTo((AFUNIXSocketAddress) socket); } + + @Override + public String addressFamilyString() { + return "AF_UNIX"; + } + + @Override + public String summaryImportantMessage(String message) { + return message; + } + + @Override + public ServerSocketChannel newServerSocketChannel() throws IOException { + return AFUNIXServerSocketChannel.open(); + } } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/AbstractNamespaceTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/AbstractNamespaceTest.java index 151ba5215..59aa4f976 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/AbstractNamespaceTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/AbstractNamespaceTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/AcceptTimeoutTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/AcceptTimeoutTest.java index 4fdae4da7..e431b98ce 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/AcceptTimeoutTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/AcceptTimeoutTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/AvailableTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/AvailableTest.java index ca0212895..a3a93c75c 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/AvailableTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/AvailableTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/BufferOverflowTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/BufferOverflowTest.java index 2b57aa390..796ba7be6 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/BufferOverflowTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/BufferOverflowTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/CancelAcceptTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/CancelAcceptTest.java index 9d2dad683..fd2beafac 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/CancelAcceptTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/CancelAcceptTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/DatagramSocketTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/DatagramSocketTest.java index 7d7a83002..61ba3817b 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/DatagramSocketTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/DatagramSocketTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,11 +17,29 @@ */ package org.newsclub.net.unix.domain; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; +import org.newsclub.net.unix.AFDatagramSocket; import org.newsclub.net.unix.AFSocketCapability; import org.newsclub.net.unix.AFSocketCapabilityRequirement; +import org.newsclub.net.unix.AFSocketType; +import org.newsclub.net.unix.AFUNIXDatagramChannel; +import org.newsclub.net.unix.AFUNIXDatagramSocket; import org.newsclub.net.unix.AFUNIXSocketAddress; +import org.newsclub.net.unix.AFUNIXSocketPair; +import org.newsclub.net.unix.OperationNotSupportedSocketException; import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; +import com.kohlschutter.testutil.TestAbortedNotAnIssueException; @AFSocketCapabilityRequirement({ AFSocketCapability.CAPABILITY_UNIX_DOMAIN, AFSocketCapability.CAPABILITY_UNIX_DATAGRAMS}) @@ -32,4 +50,66 @@ public final class DatagramSocketTest extends public DatagramSocketTest() { super(AFUNIXAddressSpecifics.INSTANCE); } + + @Test + public void testSeqPacketPair() throws Exception { + AFUNIXSocketPair pair; + try { + pair = AFUNIXSocketPair.openDatagram(AFSocketType.SOCK_SEQPACKET); + } catch (OperationNotSupportedSocketException e) { + throw new TestAbortedNotAnIssueException("SEQPACKET not supported", e); + } + + String msg = "Hello World"; + + ByteBuffer buf = ByteBuffer.wrap(msg.getBytes(StandardCharsets.UTF_8)); + ByteBuffer dst = ByteBuffer.allocate(64); + pair.getSocket1().send(buf, null); + int r = pair.getSocket2().read(dst); + assertEquals(buf.limit(), r); + dst.flip(); + assertEquals(buf.limit(), dst.limit()); + + assertEquals(msg, StandardCharsets.UTF_8.decode(dst).toString()); + } + + @Test + public void testSeqPacket() throws Exception { + boolean gotInstance = false; + try (AFUNIXDatagramSocket s1 = AFUNIXDatagramSocket.newInstance(AFSocketType.SOCK_SEQPACKET)) { + gotInstance = true; + AFUNIXSocketAddress addr = AFUNIXSocketAddress.ofNewTempFile(); + s1.bind(addr); + s1.listen(0); + + CompletableFuture cf = CompletableFuture.runAsync(() -> { + try { + AFDatagramSocket s; + s = s1.accept(); + + ByteBuffer bb = ByteBuffer.allocate(1024).order(ByteOrder.LITTLE_ENDIAN); + s.getChannel().receive(bb); + bb.flip(); + assertEquals(4, bb.remaining()); + assertEquals(0x04030201, bb.getInt()); + } catch (IOException e) { + fail(e); + } + }); + + try (AFUNIXDatagramSocket s2 = AFUNIXDatagramSocket.newInstance( + AFSocketType.SOCK_SEQPACKET)) { + s2.connect(addr); + + s2.getChannel().send(ByteBuffer.wrap(new byte[] {1, 2, 3, 4}), addr); + } + + cf.get(5, TimeUnit.SECONDS); + + } catch (OperationNotSupportedSocketException e) { + if (!gotInstance) { + throw new TestAbortedNotAnIssueException("SEQPACKET not supported", e); + } + } + } } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/EndOfFileTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/EndOfFileTest.java index cc98adb62..bcb0e9e47 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/EndOfFileTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/EndOfFileTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/FileDescriptorCastTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/FileDescriptorCastTest.java index 0647d9577..3fceafcc7 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/FileDescriptorCastTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/FileDescriptorCastTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import java.io.BufferedReader; import java.io.FileDescriptor; +import java.io.IOException; import java.io.InputStreamReader; import java.lang.ProcessBuilder.Redirect; import java.net.Socket; @@ -38,6 +39,8 @@ import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; @@ -52,10 +55,15 @@ import org.newsclub.net.unix.AFUNIXSocketPair; import org.newsclub.net.unix.FileDescriptorCast; import org.newsclub.net.unix.StdinSocketApp; +import org.newsclub.net.unix.TestUtil; +import org.opentest4j.AssertionFailedError; import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; import com.kohlschutter.testutil.ForkedVM; +import com.kohlschutter.testutil.ForkedVMRequirement; import com.kohlschutter.testutil.ProcessUtilRequirement; +import com.kohlschutter.testutil.TestAbortedWithImportantMessageException; +import com.kohlschutter.testutil.TestAbortedWithImportantMessageException.MessageType; @AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DOMAIN) @SuppressWarnings("PMD.CouplingBetweenObjects") @@ -65,51 +73,72 @@ public class FileDescriptorCastTest { @Test @AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_NATIVE_SOCKETPAIR) public void testSocketPair() throws Exception { - AFUNIXSocketPair socketPair = AFUNIXSocketPair.open(); - AFUNIXSocketChannel sock1chan = socketPair.getSocket1(); - FileDescriptor sock2fd = socketPair.getSocket2().getFileDescriptor(); + try (AFUNIXSocketPair socketPair = AFUNIXSocketPair.open()) { + AFUNIXSocketChannel sock1chan = socketPair.getSocket1(); + FileDescriptor sock2fd = socketPair.getSocket2().getFileDescriptor(); - assertTrue(sock1chan.isConnected()); + assertTrue(sock1chan.isConnected()); - FileDescriptorCast fdc = FileDescriptorCast.using(sock2fd); + FileDescriptorCast fdc = FileDescriptorCast.using(sock2fd); - // Limitation: When we emulate socket pairs through non AF_UNIX sockets, we currently do not - // support casting to Socket.class - assertEquals(AFUNIXSocket.supports(AFSocketCapability.CAPABILITY_NATIVE_SOCKETPAIR), fdc - .isAvailable(Socket.class)); + // Limitation: When we emulate socket pairs through non AF_UNIX sockets, we currently do not + // support casting to Socket.class + assertEquals(AFUNIXSocket.supports(AFSocketCapability.CAPABILITY_NATIVE_SOCKETPAIR), fdc + .isAvailable(Socket.class)); - if (!AFUNIXSocket.supports(AFSocketCapability.CAPABILITY_NATIVE_SOCKETPAIR)) { - FileDescriptorCast.using(sock2fd).as(FileChannel.class); - } else { - Socket sock2 = FileDescriptorCast.using(sock2fd).as(Socket.class); - assertEquals(AFUNIXSocket.class, sock2.getClass()); - assertTrue(sock2.isConnected()); - assertEquals(sock2fd, ((AFUNIXSocket) sock2).getFileDescriptor()); + if (!AFUNIXSocket.supports(AFSocketCapability.CAPABILITY_NATIVE_SOCKETPAIR)) { + FileDescriptorCast.using(sock2fd).as(FileChannel.class); + } else { + Socket sock2 = FileDescriptorCast.using(sock2fd).as(Socket.class); + assertEquals(AFUNIXSocket.class, sock2.getClass()); + + assertEquals(sock2fd, ((AFUNIXSocket) sock2).getFileDescriptor()); + try { + assertTrue(sock2.isConnected()); + } catch (AssertionFailedError e) { + if (TestUtil.isHaikuOS()) { + throw TestUtil.haikuBug18534(e); + } else { + throw e; + } + } + } } } @Test @AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_NATIVE_SOCKETPAIR) public void testSocketPairNative() throws Exception { - AFUNIXSocketPair socketPair = AFUNIXSocketPair.open(); - AFUNIXSocketChannel sock1chan = socketPair.getSocket1(); - FileDescriptor sock2fd = socketPair.getSocket2().getFileDescriptor(); + try (AFUNIXSocketPair socketPair = AFUNIXSocketPair.open()) { + AFUNIXSocketChannel sock1chan = socketPair.getSocket1(); + FileDescriptor sock2fd = socketPair.getSocket2().getFileDescriptor(); - assertTrue(sock1chan.isConnected()); + assertTrue(sock1chan.isConnected()); - AFUNIXSocketChannel sock2chan = FileDescriptorCast.using(sock2fd).as(AFUNIXSocketChannel.class); - assertEquals(sock2fd, sock2chan.getFileDescriptor()); - assertTrue(sock2chan.isConnected()); + AFUNIXSocketChannel sock2chan = FileDescriptorCast.using(sock2fd).as( + AFUNIXSocketChannel.class); + assertEquals(sock2fd, sock2chan.getFileDescriptor()); - ByteBuffer bb = ByteBuffer.allocate(32); - bb.putInt(0x12345678); - bb.flip(); - sock1chan.write(bb); - bb.clear(); + try { + assertTrue(sock2chan.isConnected()); + } catch (AssertionFailedError e) { + if (TestUtil.isHaikuOS()) { + throw TestUtil.haikuBug18534(e); + } else { + throw e; + } + } + + ByteBuffer bb = ByteBuffer.allocate(32); + bb.putInt(0x12345678); + bb.flip(); + sock1chan.write(bb); + bb.clear(); - assertEquals(4, sock2chan.read(bb)); - bb.flip(); - assertEquals(0x12345678, bb.getInt()); + assertEquals(4, sock2chan.read(bb)); + bb.flip(); + assertEquals(0x12345678, bb.getInt()); + } } @Test @@ -167,9 +196,17 @@ public void testServer() throws Exception { AFUNIXSocket sock = AFUNIXSocket.connectTo(serverAddress)) { AFUNIXSocket sock1 = FileDescriptorCast.using(sock.getFileDescriptor()).as( AFUNIXSocket.class); - assertEquals(sock.isBound(), sock1.isBound()); - assertEquals(sock.isConnected(), sock1.isConnected()); - assertTrue(sock1.isConnected()); + try { + assertEquals(sock.isBound(), sock1.isBound()); + assertEquals(sock.isConnected(), sock1.isConnected()); + assertTrue(sock1.isConnected()); + } catch (AssertionFailedError e) { + if (TestUtil.isHaikuOS()) { + throw TestUtil.haikuBug18534(e); + } else { + throw e; + } + } assertEquals(sock.getRemoteSocketAddress(), sock1.getRemoteSocketAddress()); assertEquals(ass.getLocalSocketAddress(), sock.getRemoteSocketAddress()); @@ -220,42 +257,65 @@ public void testDatagramSocket() throws Exception { dc1c.connect(addr2); dc2c.connect(addr1); assertTrue(dc1c.isConnected()); - assertTrue(dc1.isConnected()); - - assertEquals(dc1.getLocalAddress(), dc2c.getRemoteAddress()); - assertEquals(dc1.getRemoteAddress(), dc2c.getLocalAddress()); - assertEquals(dc1c.getLocalAddress(), dc2.getRemoteAddress()); - assertEquals(dc1c.getRemoteAddress(), dc2.getLocalAddress()); assertEquals(4, dc1c.write(bb)); bb.clear(); assertEquals(4, dc2c.read(bb)); bb.flip(); assertEquals(1234, bb.getInt()); + + try { + assertTrue(dc1.isConnected()); + assertEquals(dc1.getLocalAddress(), dc2c.getRemoteAddress()); + assertEquals(dc1.getRemoteAddress(), dc2c.getLocalAddress()); + assertEquals(dc1c.getLocalAddress(), dc2.getRemoteAddress()); + assertEquals(dc1c.getRemoteAddress(), dc2.getLocalAddress()); + } catch (AssertionFailedError e) { + if (TestUtil.isHaikuOS()) { + throw TestUtil.haikuBug18534(e); + } else { + throw e; + } + } } } @Test + @SuppressFBWarnings("DCN_NULLPOINTER_EXCEPTION") public void testSocketPorts() throws Exception { AFUNIXSocketAddress addr = AFUNIXSocketAddress.ofNewTempPath(123); try (AFUNIXServerSocket ass = AFUNIXServerSocket.bindOn(addr)) { assertEquals(123, ass.getLocalPort()); + @SuppressWarnings("checkstyle:VariableDeclarationUsageDistance") AFUNIXServerSocket ass1 = FileDescriptorCast.using(ass.getFileDescriptor()).withLocalPort(123) .as(AFUNIXServerSocket.class); assertEquals(123, ass.getLocalPort()); - AFUNIXSocket socket = AFUNIXSocket.connectTo(addr); - assertEquals(123, socket.getRemoteSocketAddress().getPort()); + try (AFUNIXSocket socket = AFUNIXSocket.connectTo(addr)) { + AFUNIXSocketAddress rsa = socket.getRemoteSocketAddress(); - AFUNIXSocket socket1 = FileDescriptorCast.using(socket.getFileDescriptor()).withRemotePort( - 123).as(AFUNIXSocket.class); - assertEquals(socket.getRemoteSocketAddress(), socket1.getRemoteSocketAddress()); - assertEquals(123, socket1.getRemoteSocketAddress().getPort()); + try { + Objects.requireNonNull(rsa); + } catch (NullPointerException e) { + if (TestUtil.isHaikuOS()) { + throw TestUtil.haikuBug18534(e); + } else { + throw e; // NOPMD.AvoidThrowingNullPointerException + } + } + + assertEquals(123, rsa.getPort()); + + AFUNIXSocket socket1 = FileDescriptorCast.using(socket.getFileDescriptor()).withRemotePort( + 123).as(AFUNIXSocket.class); + assertEquals(socket.getRemoteSocketAddress(), socket1.getRemoteSocketAddress()); + assertEquals(123, socket1.getRemoteSocketAddress().getPort()); - AFUNIXSocket ss = ass1.accept(); - assertEquals(123, ss.getLocalPort()); - assertEquals(123, ss.getLocalSocketAddress().getPort()); + AFUNIXSocket ss = ass1.accept(); + assertEquals(123, ss.getLocalPort()); + assertEquals(123, ss.getLocalSocketAddress().getPort()); + } } } @@ -285,7 +345,15 @@ public void testDatagramPorts() throws Exception { dc1.bind(addr); dc2.connect(addr); - assertEquals(dc1.getLocalAddress(), dc2.getRemoteAddress()); + try { + assertEquals(dc1.getLocalAddress(), dc2.getRemoteAddress()); + } catch (AssertionFailedError e) { + if (TestUtil.isHaikuOS() && dc2.getRemoteAddress() == null) { + throw TestUtil.haikuBug18534(e); + } else { + throw e; + } + } assertEquals(123, getPort(dc1.getLocalAddress())); @@ -307,8 +375,16 @@ public void testDatagramPorts() throws Exception { AFUNIXDatagramChannel dc2c = FileDescriptorCast.using(dc2.getFileDescriptor()) .withRemotePort(123).as(AFUNIXDatagramChannel.class); - assertEquals(123, getPort(dc1c.getLocalAddress())); - assertEquals(123, getPort(dc2c.getRemoteAddress())); + if (dc1c.getLocalAddress() != null) { // address may be null on IBM i + assertEquals(123, getPort(dc1c.getLocalAddress())); + } + int remotePort = getPort(dc2c.getRemoteAddress()); + if (remotePort == -1) { + // that's acceptable, too (seen on z/OS) + assertNull(dc2c.getRemoteAddress()); + } else { + assertEquals(123, remotePort); + } } } } @@ -346,13 +422,14 @@ public void testDatagramFileChannel() throws Exception { /** * Passes a socket to a newly created Java process as "standard input". The new process sends back * "Hello world" over that socket. - * + * * @throws Exception on failure. * @see StdinSocketApp */ @Test @AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_FD_AS_REDIRECT) @ProcessUtilRequirement(canGetJavaCommandArguments = true) + @ForkedVMRequirement(forkSupported = true) public void testForkedVMRedirectStdin() throws Exception { AFUNIXSocketAddress addr = AFUNIXSocketAddress.ofNewTempFile(); try (AFUNIXServerSocket serverSocket = AFUNIXServerSocket.bindOn(addr); @@ -365,7 +442,36 @@ public void testForkedVMRedirectStdin() throws Exception { Redirect.class)); vm.setRedirectError(Redirect.INHERIT); // vm.setRedirectOutput(Redirect.INHERIT); - Process p = vm.fork(); + + CompletableFuture cf = CompletableFuture.supplyAsync(() -> { + try { + return vm.fork(); + } catch (UnsupportedOperationException | IOException e) { + return e; + } + }); + + Object processObject; + try { + processObject = cf.get(10, TimeUnit.SECONDS); + } catch (Exception e) { + throw new TestAbortedWithImportantMessageException(MessageType.TEST_ABORTED_INFORMATIONAL, + "Environment may not support forking new processes, which one test requires", e); + } + if (!(processObject instanceof Process)) { + if (processObject instanceof Exception) { + throw (Exception) processObject; + } else if (processObject instanceof Throwable) { + throw new TestAbortedWithImportantMessageException(MessageType.TEST_ABORTED_INFORMATIONAL, + "Environment may not support forking new processes, which one test requires", + (Throwable) processObject); + } else { + throw new TestAbortedWithImportantMessageException(MessageType.TEST_ABORTED_INFORMATIONAL, + "Environment may not support forking new processes, which one test requires"); + } + } + + Process p = (Process) processObject; try { try (BufferedReader br = new BufferedReader(new InputStreamReader(serverConn .getInputStream(), StandardCharsets.UTF_8))) { @@ -375,7 +481,18 @@ public void testForkedVMRedirectStdin() throws Exception { fail("Unexpected output: " + l); } } - assertTrue(p.waitFor(30, TimeUnit.SECONDS)); + + long time = System.currentTimeMillis(); + long elapsed; + while ((elapsed = (System.currentTimeMillis() - time)) < 30 * 1000L) { + if (p.waitFor(1, TimeUnit.SECONDS)) { + break; + } + if (elapsed > 5000) { + System.out.println("Still waiting for process to terminate: " + p); + } + } + assertEquals(0, p.exitValue()); } finally { p.destroyForcibly(); diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/FileDescriptorsTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/FileDescriptorsTest.java index bc548aa60..8cfac5e80 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/FileDescriptorsTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/FileDescriptorsTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,8 @@ */ package org.newsclub.net.unix.domain; +import static java.nio.charset.StandardCharsets.UTF_8; + import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -32,7 +34,7 @@ import java.io.OutputStream; import java.net.DatagramPacket; import java.net.SocketException; -import java.nio.charset.StandardCharsets; +import java.nio.ByteBuffer; import java.nio.file.Files; import java.time.Duration; @@ -44,6 +46,7 @@ import org.newsclub.net.unix.AFUNIXDatagramSocket; import org.newsclub.net.unix.AFUNIXSocket; import org.newsclub.net.unix.AFUNIXSocketAddress; +import org.newsclub.net.unix.AFUNIXSocketChannel; import org.newsclub.net.unix.ImplUtil; import org.newsclub.net.unix.SocketTestBase; @@ -54,7 +57,7 @@ /** * Tests sending and receiving file descriptors. - * + * * @author Christian Kohlschütter */ @AFSocketCapabilityRequirement({ @@ -76,7 +79,7 @@ protected void handleConnection(final AFUNIXSocket socket) throws IOException { socket.setOutboundFileDescriptors(FileDescriptor.in, FileDescriptor.err); assertTrue(socket.hasOutboundFileDescriptors()); try (OutputStream outputStream = socket.getOutputStream()) { - outputStream.write("HELLO".getBytes("UTF-8")); + outputStream.write("HELLO".getBytes(UTF_8)); } assertFalse(socket.hasOutboundFileDescriptors()); @@ -95,7 +98,7 @@ protected void handleConnection(final AFUNIXSocket socket) throws IOException { numRead = in.read(buf); assertEquals(5, numRead, "'HELLO' is five bytes long"); - assertEquals("HELLO", new String(buf, 0, numRead, "UTF-8")); + assertEquals("HELLO", new String(buf, 0, numRead, UTF_8)); fds = socket.getReceivedFileDescriptors(); assertEquals(2, fds.length, "Now, we should have two file descriptors"); @@ -115,6 +118,114 @@ protected void handleConnection(final AFUNIXSocket socket) throws IOException { }); } + @SuppressWarnings("cast") + @Test + public void testSendRecvFileDescriptorsChannel() throws Exception { + assertTimeoutPreemptively(Duration.ofSeconds(2), () -> { + try (AFUNIXServerThread serverThread = new AFUNIXServerThread() { + @Override + protected void handleConnection(final AFUNIXSocket socket) throws IOException { + AFUNIXSocketChannel channel = socket.getChannel(); + channel.setOutboundFileDescriptors(FileDescriptor.in, FileDescriptor.err); + assertTrue(socket.hasOutboundFileDescriptors()); + ByteBuffer bb = ByteBuffer.allocate(64); + bb.put("HELLO".getBytes(UTF_8)); + bb.flip(); + channel.write(bb); + assertFalse(socket.hasOutboundFileDescriptors()); + + stopAcceptingConnections(); + } + }; + AFUNIXSocketChannel channel = ((AFUNIXSocket) connectTo(serverThread.getServerAddress())) + .getChannel()) { + channel.setAncillaryReceiveBufferSize(1024); + + FileDescriptor[] fds; + int numRead; + fds = channel.getReceivedFileDescriptors(); + assertArrayEquals(new FileDescriptor[0], fds, "Initially, there are no file descriptors"); + + ByteBuffer buf = ByteBuffer.allocate(64); + + numRead = channel.read(buf); + assertEquals(5, numRead, "'HELLO' is five bytes long"); + assertEquals("HELLO", new String((byte[]) buf.flip().array(), 0, numRead, UTF_8)); + + fds = channel.getReceivedFileDescriptors(); + assertEquals(2, fds.length, "Now, we should have two file descriptors"); + + fds = channel.getReceivedFileDescriptors(); + assertArrayEquals(new FileDescriptor[0], fds, + "If we ask again, these new file descriptors should be gone"); + + buf.clear(); + numRead = channel.read(buf); + assertEquals(-1, numRead, "There shouldn't be anything left to read"); + fds = channel.getReceivedFileDescriptors(); + assertArrayEquals(new FileDescriptor[0], fds, + "There shouldn't be any new file descriptors"); + } + }); + } + + @SuppressWarnings("cast") + @Test + public void testSendRecvFileDescriptorsChannelNonBlocking() throws Exception { + assertTimeoutPreemptively(Duration.ofSeconds(2), () -> { + try (AFUNIXServerThread serverThread = new AFUNIXServerThread() { + @Override + protected void handleConnection(final AFUNIXSocket socket) throws IOException { + AFUNIXSocketChannel channel = socket.getChannel(); + channel.configureBlocking(false); + channel.setOutboundFileDescriptors(FileDescriptor.in, FileDescriptor.err); + assertTrue(socket.hasOutboundFileDescriptors()); + ByteBuffer bb = ByteBuffer.allocate(64); + bb.put("HELLO".getBytes(UTF_8)); + bb.flip(); + channel.write(bb); + assertFalse(socket.hasOutboundFileDescriptors()); + + stopAcceptingConnections(); + } + }; + AFUNIXSocketChannel channel = ((AFUNIXSocket) connectTo(serverThread.getServerAddress())) + .getChannel()) { + channel.configureBlocking(false); + channel.setAncillaryReceiveBufferSize(1024); + + FileDescriptor[] fds; + int numRead; + fds = channel.getReceivedFileDescriptors(); + assertArrayEquals(new FileDescriptor[0], fds, "Initially, there are no file descriptors"); + + ByteBuffer buf = ByteBuffer.allocate(64); + + do { + numRead = channel.read(buf); + } while (numRead >= 0 && buf.position() < 5); + assertEquals(5, buf.position(), "'HELLO' is five bytes long"); + assertEquals("HELLO", new String((byte[]) buf.flip().array(), 0, numRead, UTF_8)); + + fds = channel.getReceivedFileDescriptors(); + assertEquals(2, fds.length, "Now, we should have two file descriptors"); + + fds = channel.getReceivedFileDescriptors(); + assertArrayEquals(new FileDescriptor[0], fds, + "If we ask again, these new file descriptors should be gone"); + + buf.clear(); + numRead = channel.read(buf); + if (numRead != 0) { + assertEquals(-1, numRead, "There shouldn't be anything left to read"); + } + fds = channel.getReceivedFileDescriptors(); + assertArrayEquals(new FileDescriptor[0], fds, + "There shouldn't be any new file descriptors"); + } + }); + } + @Test public void testNullFileDescriptorArray() throws Exception { assertTimeoutPreemptively(Duration.ofSeconds(2), () -> { @@ -294,12 +405,12 @@ public void testFileInputStream() throws Exception { @Override protected void handleConnection(final AFUNIXSocket socket) throws IOException { try (FileOutputStream fos = new FileOutputStream(tmpFile)) { - fos.write("WORLD!".getBytes("UTF-8")); + fos.write("WORLD!".getBytes(UTF_8)); } try (FileInputStream fin = new FileInputStream(tmpFile)) { socket.setOutboundFileDescriptors(fin.getFD()); try (OutputStream outputStream = socket.getOutputStream()) { - outputStream.write("HELLO".getBytes("UTF-8")); + outputStream.write("HELLO".getBytes(UTF_8)); } } @@ -316,7 +427,7 @@ protected void handleConnection(final AFUNIXSocket socket) throws IOException { numRead = in.read(buf); assertEquals(5, numRead, "'HELLO' is five bytes long"); - assertEquals("HELLO", new String(buf, 0, numRead, "UTF-8")); + assertEquals("HELLO", new String(buf, 0, numRead, UTF_8)); fds = socket.getReceivedFileDescriptors(); assertEquals(1, fds.length, "Now, we should have two file descriptors"); @@ -325,7 +436,7 @@ protected void handleConnection(final AFUNIXSocket socket) throws IOException { try (FileInputStream fin = new FileInputStream(fdesc)) { numRead = fin.read(buf); assertEquals(6, numRead, "'WORLD!' is six bytes long"); - assertEquals("WORLD!", new String(buf, 0, numRead, "UTF-8")); + assertEquals("WORLD!", new String(buf, 0, numRead, UTF_8)); } } finally { Files.deleteIfExists(tmpFile.toPath()); @@ -343,7 +454,7 @@ public void testFileInputStreamPartiallyConsumed() throws Exception { @Override protected void handleConnection(final AFUNIXSocket socket) throws IOException { try (FileOutputStream fos = new FileOutputStream(tmpFile)) { - fos.write("WORLD!".getBytes("UTF-8")); + fos.write("WORLD!".getBytes(UTF_8)); } try (FileInputStream fin = new FileInputStream(tmpFile)) { assertEquals('W', fin.read()); @@ -351,7 +462,7 @@ protected void handleConnection(final AFUNIXSocket socket) throws IOException { // We send the file descriptor of fin, from which we already consumed one byte. socket.setOutboundFileDescriptors(fin.getFD()); try (OutputStream outputStream = socket.getOutputStream()) { - outputStream.write("HELLO".getBytes("UTF-8")); + outputStream.write("HELLO".getBytes(UTF_8)); } } @@ -368,7 +479,7 @@ protected void handleConnection(final AFUNIXSocket socket) throws IOException { numRead = in.read(buf); assertEquals(5, numRead, "'HELLO' is five bytes long"); - assertEquals("HELLO", new String(buf, 0, numRead, "UTF-8")); + assertEquals("HELLO", new String(buf, 0, numRead, UTF_8)); fds = socket.getReceivedFileDescriptors(); assertEquals(1, fds.length, "Now, we should have two file descriptors"); @@ -377,7 +488,7 @@ protected void handleConnection(final AFUNIXSocket socket) throws IOException { try (FileInputStream fin = new FileInputStream(fdesc)) { numRead = fin.read(buf); assertEquals(5, numRead, "'ORLD!' is five bytes long"); - assertEquals("ORLD!", new String(buf, 0, numRead, "UTF-8")); + assertEquals("ORLD!", new String(buf, 0, numRead, UTF_8)); } } finally { Files.deleteIfExists(tmpFile.toPath()); @@ -414,16 +525,16 @@ public void testDatagramSocket() throws Exception { assertEquals(1, fds.length); try (FileOutputStream fos2 = new FileOutputStream(fds[0])) { - fos.write("Hello".getBytes(StandardCharsets.UTF_8)); + fos2.write("Hello".getBytes(UTF_8)); // closing the received file descriptor will not close the original one ... } // ... which is why we can append the data here - fos.write("World".getBytes(StandardCharsets.UTF_8)); + fos.write("World".getBytes(UTF_8)); } try (FileInputStream fin = new FileInputStream(tmpOut)) { - String text = new String(IOUtil.readAllBytes(fin), StandardCharsets.UTF_8); + String text = new String(IOUtil.readAllBytes(fin), UTF_8); // ... and the final output will contain both parts assertEquals("HelloWorld", text); } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/FinalizeTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/FinalizeTest.java index 9c910db6f..58bddb294 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/FinalizeTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/FinalizeTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ */ package org.newsclub.net.unix.domain; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; @@ -25,6 +24,10 @@ import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; import org.newsclub.net.unix.AFSocketCapability; import org.newsclub.net.unix.AFSocketCapabilityRequirement; @@ -47,67 +50,99 @@ protected String socketType() { } @SuppressFBWarnings({"RV_DONT_JUST_NULL_CHECK_READLINE"}) - private static int lsofUnixSockets(long pid) throws IOException, TestAbortedException, + private static List lsofUnixSockets(long pid) throws IOException, TestAbortedException, InterruptedException { assertTrue(pid > 0); + List lines = new ArrayList<>(); + Process p; try { p = Runtime.getRuntime().exec(new String[] {"lsof", "-U", "-a", "-p", String.valueOf(pid)}); } catch (Exception e) { assumeTrue(false, e.getMessage()); - return -1; + return Collections.emptyList(); } - int lines = 0; try (BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream(), Charset .defaultCharset()))) { String l; + + boolean hasUnix = false; + while ((l = in.readLine()) != null) { - lines++; + lines.add(l); + if (!hasUnix && l.contains("unix")) { + hasUnix = true; + } if (l.contains("busybox")) { assumeTrue(false, "incompatible lsof binary"); } } + + if (hasUnix) { + // if "lsof" returns a "unix" identifier, focus on those lines specifically. + for (Iterator it = lines.iterator(); it.hasNext();) { + String line = it.next(); + if (!line.contains("unix")) { + it.remove(); + } + } + } + + p.waitFor(); + } finally { + p.destroy(); + assumeTrue(p.exitValue() == 0, "lsof should terminate with RC=0"); } - assumeTrue(p.waitFor() == 0, "lsof should terminate with RC=0"); return lines; } @Override protected Object preRunCheck(Process process) throws TestAbortedException, IOException, InterruptedException { - int linesBefore = lsofUnixSockets(process.pid()); + List linesBefore = lsofUnixSockets(process.pid()); // If that's not true, we need to skip the test - assumeTrue(linesBefore > 0); + assumeTrue(!linesBefore.isEmpty()); return linesBefore; } + @SuppressFBWarnings("DM_GC") @Override protected void postRunCheck(Process process, Object linesBeforeObj) throws TestAbortedException, IOException, InterruptedException { - assertNotNull(linesBeforeObj); - int linesBefore = (int) linesBeforeObj; + assumeTrue(linesBeforeObj != null, "Environment does not support lsof check"); + + @SuppressWarnings("unchecked") + List linesBefore = (List) linesBeforeObj; try { - int linesAfter = 0; - for (int i = 0; i < 10; i++) { + List linesAfter = null; + for (int i = 0; i < 50; i++) { Thread.sleep(100); - linesAfter = lsofUnixSockets(process.pid()); - if (linesAfter != linesBefore) { + if (!process.isAlive()) { break; } - if (!process.isAlive()) { + if (i == 20) { + System.gc(); // NOPMD + } + linesAfter = lsofUnixSockets(process.pid()); + if (linesBefore == null || linesAfter.size() < linesBefore.size()) { break; } } - assumeTrue(linesAfter > 0, "lsof may fail to return anything"); - - assertTrue(linesAfter < linesBefore, - "Our unix socket file handle should have been cleared out"); + if (linesAfter != null && linesBefore != null && !linesBefore.isEmpty() && !linesAfter + .isEmpty()) { + if (linesAfter.size() >= linesBefore.size()) { + System.err.println("lsof: Unexpected output"); + System.err.println("lsof: Output before: " + linesBefore); + System.err.println("lsof: Output after: " + linesAfter); + } + assertTrue(linesAfter.size() < linesBefore.size(), + "Our unix socket file handle should have been cleared out"); + } } finally { process.destroy(); process.waitFor(); } } - } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/InterruptIssue158Test.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/InterruptIssue158Test.java new file mode 100644 index 000000000..02b1ea58f --- /dev/null +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/InterruptIssue158Test.java @@ -0,0 +1,52 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.domain; + +import java.io.FileNotFoundException; + +import org.newsclub.net.unix.AFSocketCapability; +import org.newsclub.net.unix.AFSocketCapabilityRequirement; +import org.newsclub.net.unix.AFUNIXSocketAddress; + +import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; + +/** + * Test interrupt-related behavior, as discussed in + * issue 158. + * + * @author https://github.com/cenodis + * @author Christian Kohlschütter + */ +@AFSocketCapabilityRequirement({AFSocketCapability.CAPABILITY_UNIX_DOMAIN}) +@SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_SUPERCLASS") +public class InterruptIssue158Test extends + org.newsclub.net.unix.InterruptIssue158Test { + + public InterruptIssue158Test() { + super(AFUNIXAddressSpecifics.INSTANCE); + } + + @Override + protected void deleteSocketFile(AFUNIXSocketAddress sa) { + try { + sa.getFile().delete(); + } catch (FileNotFoundException e) { + // ignore + } + } +} \ No newline at end of file diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/InterruptTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/InterruptTest.java new file mode 100644 index 000000000..972d594ad --- /dev/null +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/InterruptTest.java @@ -0,0 +1,32 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.domain; + +import org.newsclub.net.unix.AFSocketCapability; +import org.newsclub.net.unix.AFSocketCapabilityRequirement; +import org.newsclub.net.unix.AFUNIXSocketAddress; + +import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; + +@AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DOMAIN) +@SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_SUPERCLASS") +public class InterruptTest extends org.newsclub.net.unix.InterruptTest { + protected InterruptTest() { + super(AFUNIXAddressSpecifics.INSTANCE); + } +} diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/MassiveParallelTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/MassiveParallelTest.java new file mode 100644 index 000000000..3fd330319 --- /dev/null +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/MassiveParallelTest.java @@ -0,0 +1,344 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.domain; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; +import org.newsclub.net.unix.AFSocketCapability; +import org.newsclub.net.unix.AFSocketCapabilityRequirement; +import org.newsclub.net.unix.AFUNIXServerSocket; +import org.newsclub.net.unix.AFUNIXSocket; +import org.newsclub.net.unix.AFUNIXSocketAddress; +import org.newsclub.net.unix.AFUNIXSocketChannel; +import org.newsclub.net.unix.ConnectionResetSocketException; +import org.newsclub.net.unix.SocketClosedException; +import org.newsclub.net.unix.ThreadUtil; + +import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; +import com.kohlschutter.testutil.TestAbortedNotAnIssueException; +import com.kohlschutter.util.SystemPropertyUtil; + +@AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DOMAIN) +@SuppressWarnings({ + "PMD.NcssCount", "PMD.CognitiveComplexity", "PMD.CyclomaticComplexity", "PMD.NPathComplexity"}) +@SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_SUPERCLASS") +public class MassiveParallelTest extends + org.newsclub.net.unix.MassiveParallelTest { + private static final int MAX_SERVER_THREADS = 32; + + protected MassiveParallelTest() { + super(AFUNIXAddressSpecifics.INSTANCE); + } + + @Test + @SuppressWarnings({"PMD.CognitiveComplexity", "PMD.AvoidCatchingThrowable"}) + public void testAcceptConnect() throws Exception { + if (!ThreadUtil.isVirtualThreadSupported()) { + throw new TestAbortedNotAnIssueException("Virtual Threads are not supported by this JVM"); + } + // number of connections to perform + // final int numConnections = 1_000; + // final int numConnections = 10_000; + // final int numConnections = 100_000; + // final int numConnections = 1_000_000; + final int numConnections = SystemPropertyUtil.getIntSystemProperty( + "selftest.MassiveParallelTest.numConnections", 1000); + if (numConnections <= 0) { + throw new TestAbortedNotAnIssueException("Skipping test due to numConnections=" + + numConnections); + } + + // limit the number of concurrently active servers/clients + // so we don't run out of file descriptors (the limit could be as low as 256) + final int nProc = Math.min(MAX_SERVER_THREADS, Runtime.getRuntime().availableProcessors()); + final Semaphore concurrentClientPermits = new Semaphore(Math.min(100, nProc)); + + AFUNIXSocketAddress listenAddr = AFUNIXSocketAddress.ofNewTempFile(); + + long startTime; + + try (Server server = new Server(listenAddr, nProc, numConnections)) { + ExecutorService esClients = ThreadUtil.newVirtualThreadPerTaskExecutor(); + // ExecutorService esClients = Executors.newWorkStealingPool(); + final AtomicInteger connectAttempts = new AtomicInteger(0); + final AtomicInteger connected = new AtomicInteger(0); + + Runnable clientJob = new Runnable() { + + @Override + public void run() { + try (AFUNIXSocket socket = AFUNIXSocket.newInstance()) { + concurrentClientPermits.acquire(); + try { + connectAttempts.incrementAndGet(); + socket.connect(listenAddr); + } catch (SocketTimeoutException | SocketException e) { + // try again + return; + } + + connected.incrementAndGet(); + try (InputStream in = socket.getInputStream(); + OutputStream out = socket.getOutputStream()) { + int rcv = in.read(); + if (rcv != 0xAA) { + if (server.isRunning()) { + System.err.println("Wrong data: " + rcv); + } + } + + byte[] otherBytes = new byte[5]; + int pos = 0; + int remaining = otherBytes.length; + while (remaining > 0) { + int read = in.read(otherBytes, pos, remaining); + if (read == -1) { + break; + } + remaining -= read; + pos += read; + } + if (remaining != 0) { + if (server.isRunning()) { + System.err.println("Incomplete data; bytes missing " + remaining); + } + } + if (server.isRunning()) { + if (!Arrays.equals(new byte[] {(byte) 0xAB, (byte) 0xAC, 'X', 'Y', 'Z'}, + otherBytes)) { + System.err.println("Wrong data received: " + Arrays.toString(otherBytes) + " vs " + + Arrays.toString(new byte[] {(byte) 0xAB, (byte) 0xAC, 'X', 'Y', 'Z'})); + } + } + + out.write(0xBB); + out.flush(); + } + + } catch (Throwable e) { + if (server.isRunning()) { + e.printStackTrace(); + } + } finally { + concurrentClientPermits.release(); + if (server.isRunning()) { + // keep trying + esClients.submit(this); + } + } + } + }; + + startTime = System.currentTimeMillis(); + + for (int i = 0; i < numConnections; i++) { + esClients.submit(clientJob); + } + + boolean stopped = server.cl.await(10, TimeUnit.SECONDS); + if (!stopped) { + List remainingClients = esClients.shutdownNow(); + List remainingServers = server.esServers.shutdownNow(); + System.err.println("Not all connections were made; remaining: " + server.cl.getCount()); + if (!remainingClients.isEmpty() || !remainingServers.isEmpty()) { + System.err.println("Remaining threads: servers=" + remainingServers.size() + "; clients=" + + remainingClients.size()); + } + } + server.stop(); + + long elapsed = (System.currentTimeMillis() - startTime); + int completed = server.completed.intValue(); + System.out.println("millis: " + elapsed); + System.out.println("completed: " + completed); + float timePerItem = elapsed / (float) completed; + System.out.println("time per completed connection: " + timePerItem + " ms"); + + if (completed <= nProc && completed < numConnections / 10.0) { + fail("Not enough jobs were completed: " + completed + "; expected:" + numConnections); + } + } + } + + private static final class Server implements Closeable { + final AtomicBoolean running = new AtomicBoolean(true); + final AtomicInteger accepted = new AtomicInteger(0); + final AtomicInteger serverThreads = new AtomicInteger(0); + final AtomicInteger completed = new AtomicInteger(0); + + final AtomicInteger acceptsInFlight = new AtomicInteger(0); + final AFUNIXServerSocket serverSocket; + final ExecutorService esServers; + + final CountDownLatch cl; + + public Server(AFUNIXSocketAddress listenAddr, int numServerThreads, int stopAfterNumConnections) + throws IOException { + cl = new CountDownLatch(stopAfterNumConnections); + serverSocket = AFUNIXServerSocket.newInstance(); + serverSocket.bind(listenAddr); // allow for some default backlog + + // esServers = Executors.newCachedThreadPool(); + // esServers = Executors.newWorkStealingPool(); + esServers = ThreadUtil.newVirtualThreadPerTaskExecutor(); + + for (int i = 0; i < numServerThreads; i++) { + esServers.submit(this::acceptJob); + } + } + + public boolean isRunning() { + return running.get() && !serverSocket.isClosed(); + } + + @Override + public String toString() { + return super.toString() + "[closed=" + serverSocket.isClosed() + ";running=" + running.get() + + ";inAccept=" + acceptsInFlight + ";completed=" + completed + "]"; + } + + @SuppressFBWarnings({"NP_LOAD_OF_KNOWN_NULL_VALUE", "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"}) + private void acceptJob() { + serverThreads.incrementAndGet(); + acceptsInFlight.incrementAndGet(); + try { + ByteBuffer bbNonDirect = ByteBuffer.allocate(64); + ByteBuffer bbDirect = ByteBuffer.allocateDirect(64); + + while (isRunning()) { + try (AFUNIXSocket socket = serverSocket.accept()) { + accepted.incrementAndGet(); + if (socket == null) { + continue; + } + cl.countDown(); + if (cl.getCount() == 0) { + stop(); + } + + try (AFUNIXSocketChannel channel = socket.getChannel(); + OutputStream out = socket.getOutputStream();) { + channel.configureBlocking(true); + ByteBuffer bb = bbDirect; + bb.clear(); + bb.put((byte) 0xAA); + bb.put((byte) 0xAB); + bb.put((byte) 0xAC); + bb.flip(); + + while (bb.hasRemaining()) { + channel.write(bb); + } + + out.write(new byte[] {'X', 'Y', 'Z'}); + out.flush(); + + bb = bbNonDirect; + + bb.clear(); + int read = channel.read(bb); + if (read != 1) { + if (isRunning()) { + System.err.println("Wrong response: " + read + " bytes"); + } + } else { + int rcv = bb.get(0) & 0xFF; + if (rcv != 0xbb) { + if (isRunning()) { + System.err.println("Wrong response: 0x" + Integer.toHexString(rcv)); + } + } else { + completed.incrementAndGet(); + } + } + } + } catch (InterruptedIOException | ConnectionResetSocketException | SocketClosedException + | ClosedChannelException e) { + // ignore + } catch (SocketException e) { + if (isRunning()) { + throw e; + } else { + // ignore + } + } + } + } catch (Throwable e) { // NOPMD.ExceptionAsFlowControl,AvoidCatchingThrowable + if (isRunning()) { + e.printStackTrace(); + } + } finally { + acceptsInFlight.decrementAndGet(); + if (isRunning()) { + System.err.println("Restarting failed server job"); + try { + esServers.submit(this::acceptJob); + } catch (RejectedExecutionException e) { + if (isRunning()) { + e.printStackTrace(); + } + } catch (Throwable t) { // NOPMD.AvoidCatchingThrowable + t.printStackTrace(); + } + } + } + } + + public void stop() { + running.set(false); + esServers.shutdown(); + } + + @Override + public void close() throws IOException { + stop(); + serverSocket.close(); + try { + if (!esServers.awaitTermination(5, TimeUnit.SECONDS)) { + esServers.shutdownNow(); + if (!esServers.awaitTermination(5, TimeUnit.SECONDS)) { + throw new InterruptedIOException("did not terminate"); + } + } + } catch (InterruptedException e) { + throw (InterruptedIOException) new InterruptedIOException("did not terminate").initCause(e); + } + } + } + +} diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/PeerCredentialsTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/PeerCredentialsTest.java index 8f7dc4dec..ad6475668 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/PeerCredentialsTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/PeerCredentialsTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,11 +41,13 @@ import org.newsclub.net.unix.SocketTestBase; import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; +import com.kohlschutter.testutil.TestAbortedWithImportantMessageException; +import com.kohlschutter.testutil.TestAbortedWithImportantMessageException.MessageType; import com.kohlschutter.util.ProcessUtil; /** * Verifies that peer credentials are properly set. - * + * * @author Christian Kohlschütter */ @AFSocketCapabilityRequirement({ @@ -80,7 +82,7 @@ protected void handleConnection(final AFUNIXSocket socket) throws IOException { } } }; AFUNIXSocket socket = (AFUNIXSocket) connectTo(serverThread.getServerAddress())) { - try (InputStream in = socket.getInputStream()) { + try (InputStream unused = socket.getInputStream()) { AFUNIXSocketCredentials serverCreds = socket.getPeerCredentials(); AFUNIXSocketCredentials clientCreds = clientCredsFuture.get(); @@ -189,6 +191,17 @@ public static void ensureSameCreds() { if (credsDatagramSockets.isEmpty() && !credsSockets.isEmpty()) { System.out.println("WARNING: No peer credentials for datagram sockets"); } else { + if (credsSockets != null && !credsSockets.equals(credsDatagramSockets)) { + if (credsDatagramSockets.getUid() == 0 && credsDatagramSockets.getGid() == 0) { + // seen on AIX (but not IBM i/PASE). + throw new TestAbortedWithImportantMessageException( + MessageType.TEST_ABORTED_WITH_ISSUES, + "Credentials received via AFUNIXDatagramSocket returned " + credsDatagramSockets + + ", which may not be correct; expected: " + credsSockets + + ". This could be a problem specific to your operating system"); + } + } + assertEquals(credsSockets, credsDatagramSockets, "The credentials received via Socket and via DatagramSocket should be the same"); } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/ReadWriteTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/ReadWriteTest.java index a39bebb67..58840bfa9 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/ReadWriteTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/ReadWriteTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SelectorTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SelectorTest.java index 3c7352bd2..6367356a6 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SelectorTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SelectorTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/ServerSocketCloseTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/ServerSocketCloseTest.java index bbd62c133..74f8ec4a9 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/ServerSocketCloseTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/ServerSocketCloseTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/ServerSocketTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/ServerSocketTest.java index b9c191fa2..57257c170 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/ServerSocketTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/ServerSocketTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SoTimeoutTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SoTimeoutTest.java index 35b663d37..2264cf533 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SoTimeoutTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SoTimeoutTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketAddressTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketAddressTest.java index 62af20e6b..e67e481dc 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketAddressTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketAddressTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,8 @@ import java.nio.charset.Charset; import org.junit.jupiter.api.Test; +import org.newsclub.net.unix.AFSocket; +import org.newsclub.net.unix.AFSocketCapability; import org.newsclub.net.unix.AFUNIXSocketAddress; import org.newsclub.net.unix.SocketTestBase; @@ -50,18 +52,33 @@ public SocketAddressTest() { public void testPort() throws IOException { assertEquals(0, AFUNIXSocketAddress.of(new File("/tmp/whatever")).getPort()); assertEquals(123, AFUNIXSocketAddress.of(new File("/tmp/whatever"), 123).getPort()); - assertEquals(44444, AFUNIXSocketAddress.of(new File("/tmp/whatever"), 44444).getPort()); try { AFUNIXSocketAddress.of(new File("/tmp/whatever"), -2); fail("Expected IllegalArgumentException for illegal port"); - } catch (final IllegalArgumentException e) { + } catch (IllegalArgumentException e) { // expected } + + assertEquals(44444, AFUNIXSocketAddress.of(new File("/tmp/whatever"), 44444).getPort()); + + AFUNIXSocketAddress.of(new File("/tmp/whatever"), 65535); + } + + @Test + public void testLargePort() throws Exception { + boolean supportsLargePorts = AFSocket.supports(AFSocketCapability.CAPABILITY_LARGE_PORTS); + + try { + assertEquals(544444, AFUNIXSocketAddress.of(new File("/tmp/whatever"), 544444).getPort()); + } catch (SocketException e) { + assertFalse(supportsLargePorts); + } + try { AFUNIXSocketAddress.of(new File("/tmp/whatever"), 65536); - } catch (final IllegalArgumentException e) { - fail("AFUNIXSocketAddress supports ports larger than 65535"); + } catch (SocketException e) { + assertFalse(supportsLargePorts); } } @@ -102,7 +119,7 @@ public void testAbstractNamespace() throws Exception { } assertTrue(address.isInAbstractNamespace()); assertFalse(address.hasFilename()); - assertThrows(FileNotFoundException.class, () -> address.getFile()); + assertThrows(FileNotFoundException.class, address::getFile); } @Test diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketChannelTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketChannelTest.java index a5aa7152c..506f30571 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketChannelTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketChannelTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,11 +17,28 @@ */ package org.newsclub.net.unix.domain; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.net.ProtocolFamily; +import java.net.SocketAddress; +import java.nio.channels.DatagramChannel; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +import org.junit.jupiter.api.Test; +import org.newsclub.net.unix.AFDatagramChannel; +import org.newsclub.net.unix.AFServerSocketChannel; import org.newsclub.net.unix.AFSocketCapability; import org.newsclub.net.unix.AFSocketCapabilityRequirement; +import org.newsclub.net.unix.AFSocketChannel; +import org.newsclub.net.unix.AFUNIXDatagramChannel; +import org.newsclub.net.unix.AFUNIXServerSocketChannel; import org.newsclub.net.unix.AFUNIXSocketAddress; +import org.newsclub.net.unix.AFUNIXSocketChannel; +import org.newsclub.net.unix.jep380.JEP380AddressSpecifics; import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; +import com.kohlschutter.testutil.TestAbortedNotAnIssueException; @AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DOMAIN) @SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_SUPERCLASS") @@ -31,4 +48,47 @@ public final class SocketChannelTest extends public SocketChannelTest() { super(AFUNIXAddressSpecifics.INSTANCE); } + + @Test + public void testUnixDomainProtocolFamily() throws Exception { + ProtocolFamily unix = JEP380AddressSpecifics.unixProtocolFamilyIfAvailable(); + if (unix == null) { + throw new TestAbortedNotAnIssueException( + "StandardProtocolFamily.UNIX is not supported by this VM"); + } + + // Java 16+: We can create JVM-specific AF_UNIX channels + // (omitted here for compatibilty with Java 14 and lower) + // try (SocketChannel ch = SocketChannel.open(unix)) { + // assertNotEquals(AFUNIXSocketChannel.class, ch.getClass()); + // } + + // When we call AF*Channel#open with StandardProtocolFamily.UNIX, + // we will get our implementations + + try (SocketChannel ch = AFSocketChannel.open(unix)) { + assertEquals(AFUNIXSocketChannel.class, ch.getClass()); + } + try (ServerSocketChannel ch = AFServerSocketChannel.open(unix)) { + assertEquals(AFUNIXServerSocketChannel.class, ch.getClass()); + } + try (DatagramChannel ch = AFDatagramChannel.open(unix)) { + assertEquals(AFUNIXDatagramChannel.class, ch.getClass()); + } + } + + @Override + protected boolean mayTestBindNullThrowUnsupportedOperationException() { + return false; + } + + @Override + protected boolean mayTestBindNullHaveNullLocalSocketAddress() { + return false; + } + + @Override + protected void cleanupTestBindNull(ServerSocketChannel sc, SocketAddress addr) throws Exception { + // nothing to do, as -- unlike JEP380 -- junixsocket cleans up its mess + } } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketFactoryTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketFactoryTest.java index 6e349c8b6..41ba566cf 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketFactoryTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketFactoryTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,10 @@ */ package org.newsclub.net.unix.domain; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import java.io.File; import java.net.InetAddress; @@ -25,6 +28,8 @@ import java.net.Socket; import java.net.SocketException; +import javax.net.SocketFactory; + import org.junit.jupiter.api.Test; import org.newsclub.net.unix.AFSocketCapability; import org.newsclub.net.unix.AFSocketCapabilityRequirement; @@ -45,6 +50,7 @@ public SocketFactoryTest() { super(AFUNIXAddressSpecifics.INSTANCE); } + @SuppressWarnings("AddressSelection" /* errorprone */) @Test public void testURISchemeCeateSocketThenConnect() throws Exception { AFUNIXSocketFactory.URIScheme factory = new AFUNIXSocketFactory.URIScheme(); @@ -83,11 +89,12 @@ public void testURISchemeCeateSocketWithIllegalArguments() throws Exception { assertThrows(IllegalArgumentException.class, () -> { // Illegal local port try (Socket sock = factory.createSocket("file:///", 0, null, -1)) { - // not reached + fail("Should not be reached: " + sock); } }); } + @SuppressWarnings("AddressSelection" /* errorprone */) @Test public void testURISchemeCeateSocketWithInvalidHostname() throws Exception { AFUNIXSocketFactory.URIScheme factory = new AFUNIXSocketFactory.URIScheme(); @@ -95,61 +102,61 @@ public void testURISchemeCeateSocketWithInvalidHostname() throws Exception { assertThrows(SocketException.class, () -> { // We don't support empty hosts try (Socket sock = factory.createSocket("", 0)) { - // not reached + fail("Should not be reached: " + sock); } }); assertThrows(SocketException.class, () -> { // We don't support empty hosts try (Socket sock = factory.createSocket("", 0, null, 0)) { - // not reached + fail("Should not be reached: " + sock); } }); assertThrows(SocketException.class, () -> { // We don't support IP addresses try (Socket sock = factory.createSocket(InetAddress.getByName(""), 0)) { - // not reached + fail("Should not be reached: " + sock); } }); assertThrows(SocketException.class, () -> { // We don't support IP addresses try (Socket sock = factory.createSocket(InetAddress.getLoopbackAddress(), 0, null, 0)) { - // not reached + fail("Should not be reached: " + sock); } }); assertThrows(SocketException.class, () -> { // file:// has an empty path component try (Socket sock = factory.createSocket("file:", 0)) { - // not reached + fail("Should not be reached: " + sock); } }); assertThrows(SocketException.class, () -> { // file:// has an empty path component try (Socket sock = factory.createSocket("file:/", 0)) { - // not reached + fail("Should not be reached: " + sock); } }); assertThrows(SocketException.class, () -> { // file:// has an empty path component try (Socket sock = factory.createSocket("file://", 0)) { - // not reached + fail("Should not be reached: " + sock); } }); assertThrows(SocketException.class, () -> { // file://not-absolute is not an absolute path (three slashes needed) try (Socket sock = factory.createSocket("file://not-absolute", 0)) { - // not reached + fail("Should not be reached: " + sock); } }); assertThrows(SocketException.class, () -> { // incomplete try (Socket sock = factory.createSocket("[", 0)) { - // not reached + fail("Should not be reached: " + sock); } }); assertThrows(SocketException.class, () -> { // encoded; incomplete trailing escape try (Socket sock = factory.createSocket("file%3A%2F%2F%", 0)) { - // not reached + fail("Should not be reached: " + sock); } }); } @@ -161,37 +168,37 @@ public void testURISchemeCeateSocketWithHostnameValidCases() throws Exception { assertThrows(SocketException.class, () -> { // file exists (root directory), but is definitely not a unix socket try (Socket sock = factory.createSocket("file:///", 0)) { - // not reached + fail("Should not be reached: " + sock); } }); assertThrows(SocketException.class, () -> { // file exists (root directory), but is definitely not a unix socket try (Socket sock = factory.createSocket("file://localhost/", 0)) { - // not reached + fail("Should not be reached: " + sock); } }); assertThrows(SocketException.class, () -> { // file exists (root directory), but is definitely not a unix socket try (Socket sock = factory.createSocket("[file:///]", 0)) { - // not reached + fail("Should not be reached: " + sock); } }); assertThrows(SocketException.class, () -> { // file exists (root directory), but is definitely not a unix socket try (Socket sock = factory.createSocket("[file:///", 0)) { - // not reached + fail("Should not be reached: " + sock); } }); assertThrows(SocketException.class, () -> { // encoded; file exists (root directory), but is definitely not a unix socket try (Socket sock = factory.createSocket("file%3A%2F%2F%2F", 0)) { - // not reached + fail("Should not be reached: " + sock); } }); assertThrows(SocketException.class, () -> { // encoded; file exists (root directory), but is definitely not a unix socket try (Socket sock = factory.createSocket("file%3A%2F%2Flocalhost%2F", 0)) { - // not reached + fail("Should not be reached: " + sock); } }); } @@ -209,7 +216,7 @@ public void testSystemProperty() throws Exception { assertThrows(IllegalStateException.class, () -> { // system property "org.newsclub.net.unix.socket.default" not configured try (Socket sock = factory.createSocket(hostnameToConnectTo, 0)) { - // not reached + fail("Should not be reached: " + sock); } }); @@ -217,7 +224,7 @@ public void testSystemProperty() throws Exception { assertThrows(IllegalStateException.class, () -> { // system property "org.newsclub.net.unix.socket.default" not configured try (Socket sock = factory.createSocket(hostnameToConnectTo, 0)) { - // not reached + fail("Should not be reached: " + sock); } }); @@ -225,7 +232,7 @@ public void testSystemProperty() throws Exception { assertThrows(SocketException.class, () -> { // file exists (root directory), but is definitely not a unix socket try (Socket sock = factory.createSocket(hostnameToConnectTo, 0)) { - // not reached + fail("Should not be reached: " + sock); } }); } finally { @@ -244,7 +251,7 @@ public void testFactoryArg() throws Exception { assertThrows(SocketException.class, () -> { AFUNIXSocketFactory.FactoryArg factory = new AFUNIXSocketFactory.FactoryArg("/"); // file exists (root directory), but is definitely not a unix socket - try (Socket sock = factory.createSocket(hostnameToConnectTo, 0)) { + try (Socket unused = factory.createSocket(hostnameToConnectTo, 0)) { // not reached } }); @@ -252,9 +259,38 @@ public void testFactoryArg() throws Exception { assertThrows(SocketException.class, () -> { AFUNIXSocketFactory.FactoryArg factory = new AFUNIXSocketFactory.FactoryArg(new File("/")); // file exists (root directory), but is definitely not a unix socket - try (Socket sock = factory.createSocket(hostnameToConnectTo, 0)) { + try (Socket unused = factory.createSocket(hostnameToConnectTo, 0)) { // not reached } }); } + + @Test + public void testReflection() throws Exception { + assertEquals(AFUNIXSocketFactory.SystemProperty.class.getName(), Class.forName( + "org.newsclub.net.unix.AFUNIXSocketFactory$SystemProperty").getName()); + assertEquals(AFUNIXSocketFactory.URIScheme.class.getName(), Class.forName( + "org.newsclub.net.unix.AFUNIXSocketFactory$URIScheme").getName()); + assertEquals(AFUNIXSocketFactory.FactoryArg.class.getName(), Class.forName( + "org.newsclub.net.unix.AFUNIXSocketFactory$FactoryArg").getName()); + + newInstance("org.newsclub.net.unix.AFUNIXSocketFactory$SystemProperty", null); + newInstance("org.newsclub.net.unix.AFUNIXSocketFactory$URIScheme", null); + newInstance("org.newsclub.net.unix.AFUNIXSocketFactory$FactoryArg", "/"); + newInstance("org.newsclub.net.unix.AFUNIXSocketFactory$FactoryArg", new File("/")); + } + + private static SocketFactory newInstance(String className, Object arg) throws Exception { + Class klazz = Class.forName(className); + assertTrue(SocketFactory.class.isAssignableFrom(klazz)); + + Object instance; + if (arg == null) { + instance = klazz.getConstructor().newInstance(); + } else { + instance = klazz.getConstructor(arg.getClass()).newInstance(arg); + } + + return (SocketFactory) instance; + } } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketPairTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketPairTest.java index 06d5be29c..7805d695e 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketPairTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketPairTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketTest.java index 4405a8f6c..33361e9d9 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ /** * Tests some otherwise uncovered methods of {@link AFUNIXSocket}. - * + * * @author Christian Kohlschütter */ @AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DOMAIN) diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/StandardSocketOptionsTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/StandardSocketOptionsTest.java index c50e667c3..b5c8ab8a4 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/StandardSocketOptionsTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/StandardSocketOptionsTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/TcpNoDelayTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/TcpNoDelayTest.java index 1fef1a952..dc48b3b88 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/TcpNoDelayTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/TcpNoDelayTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/ThroughputTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/ThroughputTest.java index 30fdf9027..8e0489c3e 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/ThroughputTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/ThroughputTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,14 +19,12 @@ import org.newsclub.net.unix.AFSocketCapability; import org.newsclub.net.unix.AFSocketCapabilityRequirement; -import org.newsclub.net.unix.AFUNIXSocketAddress; import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; @AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DOMAIN) @SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_SUPERCLASS") -public final class ThroughputTest extends - org.newsclub.net.unix.ThroughputTest { +public final class ThroughputTest extends ThroughputTestShim { public ThroughputTest() { super(AFUNIXAddressSpecifics.INSTANCE); diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/ThroughputTestShim.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/ThroughputTestShim.java new file mode 100644 index 000000000..e265679c0 --- /dev/null +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/ThroughputTestShim.java @@ -0,0 +1,76 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.domain; + +import java.net.StandardProtocolFamily; +import java.net.UnixDomainSocketAddress; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; +import org.newsclub.net.unix.AFUNIXSocketAddress; +import org.newsclub.net.unix.AddressSpecifics; + +import com.kohlschutter.testutil.AvailabilityRequirement; + +/** + * Shim class to allow for some tests that test code that is only available in newer Java versions, + * particularly JEP 380-related code (UnixDomainSocketAddress). + * + * @author Christian Kohlschütter + */ +abstract class ThroughputTestShim extends + org.newsclub.net.unix.ThroughputTest { + + protected ThroughputTestShim(AddressSpecifics asp) { + super(asp); + } + + @Test + @AvailabilityRequirement(classes = "java.net.UnixDomainSocketAddress", // + message = "This test requires Java 16 or later") + public void testJEP380() throws Exception { + testJEP380(false); + } + + @Test + @AvailabilityRequirement(classes = "java.net.UnixDomainSocketAddress", // + message = "This test requires Java 16 or later") + public void testJEP380direct() throws Exception { + testJEP380(true); + } + + private void testJEP380(boolean direct) throws Exception { + Path p = newTempFile().toPath(); + try { + UnixDomainSocketAddress usa = UnixDomainSocketAddress.of(p); + + ServerSocketChannel ssc = ServerSocketChannel.open(StandardProtocolFamily.UNIX); + + runTestSocketChannel("JEP380 SocketChannel", usa, ssc, () -> { + SocketChannel sc = SocketChannel.open(StandardProtocolFamily.UNIX); + connectSocket(sc, ssc.getLocalAddress()); + return sc; + }, direct); + } finally { + Files.delete(p); + } + } +} diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/UnixDomainSocketAddressTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/UnixDomainSocketAddressTest.java new file mode 100644 index 000000000..656e8cb69 --- /dev/null +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/UnixDomainSocketAddressTest.java @@ -0,0 +1,51 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.domain; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.net.UnixDomainSocketAddress; + +import org.junit.jupiter.api.Test; +import org.newsclub.net.unix.AFSocketAddress; +import org.newsclub.net.unix.AFUNIXSocketAddress; +import org.newsclub.net.unix.SocketTestBase; + +import com.kohlschutter.testutil.AvailabilityRequirement; + +public class UnixDomainSocketAddressTest extends SocketTestBase { + public UnixDomainSocketAddressTest() { + super(AFUNIXAddressSpecifics.INSTANCE); + } + + @Test + @AvailabilityRequirement(classes = "java.net.UnixDomainSocketAddress", // + message = "This test requires Java 16 or later") + public void testConvertUnixDomainSocketAddress() throws Exception { + File f = new File("/tmp/hello"); + UnixDomainSocketAddress addr = UnixDomainSocketAddress.of(f.getPath()); + + AFUNIXSocketAddress usa = AFUNIXSocketAddress.of(addr); + assertEquals(f, usa.getFile()); + + assertTrue(AFSocketAddress.canMap(addr)); + assertEquals(usa, AFSocketAddress.mapOrFail(addr)); + } +} diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/java/AcceptTimeoutTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/AcceptTimeoutTest.java index 3b84ffc43..fe704b115 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/java/AcceptTimeoutTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/AcceptTimeoutTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/java/EndOfFileJavaTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/EndOfFileJavaTest.java index 470a462e3..c32b04f65 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/java/EndOfFileJavaTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/EndOfFileJavaTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/java/InterruptIssue158Test.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/InterruptIssue158Test.java new file mode 100644 index 000000000..89432e839 --- /dev/null +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/InterruptIssue158Test.java @@ -0,0 +1,52 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.java; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; + +import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; + +/** + * Test interrupt-related behavior, as discussed in + * issue 158. + * + * @author https://github.com/cenodis + * @author Christian Kohlschütter + */ +@SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_SUPERCLASS") +public class InterruptIssue158Test extends + org.newsclub.net.unix.InterruptIssue158Test { + + public InterruptIssue158Test() { + super(JavaAddressSpecifics.INSTANCE); + } + + @Override + protected void deleteSocketFile(InetSocketAddress sa) { + + } + + @Override + protected InetSocketAddress newTempAddress() throws IOException { + try (ServerSocket ss = newServerSocketBindOn(super.newTempAddress())) { + return (InetSocketAddress) ss.getLocalSocketAddress(); + } + } +} diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/java/InterruptTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/InterruptTest.java new file mode 100644 index 000000000..809e9307e --- /dev/null +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/InterruptTest.java @@ -0,0 +1,33 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.java; + +import java.net.InetSocketAddress; + +import org.newsclub.net.unix.AFSocketCapability; +import org.newsclub.net.unix.AFSocketCapabilityRequirement; + +import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; + +@AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DOMAIN) +@SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_SUPERCLASS") +public class InterruptTest extends org.newsclub.net.unix.InterruptTest { + protected InterruptTest() { + super(JavaAddressSpecifics.INSTANCE); + } +} diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/java/JavaAddressSpecifics.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/JavaAddressSpecifics.java index 5fb8def66..61946f42e 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/java/JavaAddressSpecifics.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/JavaAddressSpecifics.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import java.net.SocketAddress; import java.net.SocketException; import java.nio.channels.DatagramChannel; +import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.channels.spi.SelectorProvider; @@ -52,7 +53,13 @@ public static SocketAddress wildcardBindAddress() throws IOException { @Override public SocketAddress newTempAddress() throws IOException { - return wildcardBindAddress(); + SocketAddress bindAddr = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); + try (ServerSocket sock = new ServerSocket()) { + sock.bind(bindAddr); + return sock.getLocalSocketAddress(); + } catch (BindException e) { + throw new TestAbortedException("Cannot bind to " + bindAddr, e); + } } @Override @@ -70,6 +77,11 @@ public DatagramSocket newDatagramSocket() throws IOException { return new DatagramSocket(); } + @Override + public SocketChannel newSocketChannel() throws IOException { + return selectorProvider().openSocketChannel(); + } + @Override public DatagramChannel newDatagramChannel() throws IOException { return selectorProvider().openDatagramChannel(); @@ -107,7 +119,7 @@ public CloseablePair newDatagramSocketPair() throws I DatagramSocket ds2 = new DatagramSocket(0); ds1.connect(ds2.getLocalSocketAddress()); ds2.connect(ds1.getLocalSocketAddress()); - return new CloseablePair(ds1.getChannel(), ds2.getChannel(), () -> { + return new CloseablePair<>(ds1.getChannel(), ds2.getChannel(), () -> { ds1.close(); ds2.close(); }); @@ -116,7 +128,8 @@ public CloseablePair newDatagramSocketPair() throws I @Override public ServerSocket newServerSocketBindOn(SocketAddress addr) throws IOException { InetSocketAddress inetAddr = (InetSocketAddress) addr; - if (!inetAddr.getAddress().isAnyLocalAddress()) { + InetAddress address = inetAddr.getAddress(); + if (!address.isAnyLocalAddress() && !address.isLoopbackAddress()) { throw new IllegalArgumentException("Not a local address: " + inetAddr); } return new ServerSocket(inetAddr.getPort()); @@ -132,9 +145,25 @@ public Socket connectTo(SocketAddress addr) throws IOException { public ServerSocket newServerSocketBindOn(SocketAddress addr, boolean deleteOnClose) throws IOException { InetSocketAddress inetAddr = (InetSocketAddress) addr; - if (!inetAddr.getAddress().isAnyLocalAddress()) { + InetAddress address = inetAddr.getAddress(); + if (!address.isAnyLocalAddress() && !address.isLoopbackAddress()) { throw new IllegalArgumentException("Not a local address: " + inetAddr); } return new ServerSocket(inetAddr.getPort()); } + + @Override + public String addressFamilyString() { + return "Java standard socket"; + } + + @Override + public String summaryImportantMessage(String message) { + return message; + } + + @Override + public ServerSocketChannel newServerSocketChannel() throws IOException { + return SelectorProvider.provider().openServerSocketChannel(); + } } diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/java/JavaInetStackCondition.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/JavaInetStackCondition.java index 892b6ecde..d51907e7d 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/java/JavaInetStackCondition.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/JavaInetStackCondition.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/java/JavaInetStackRequirement.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/JavaInetStackRequirement.java index d02485871..2533f586d 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/java/JavaInetStackRequirement.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/JavaInetStackRequirement.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,10 +26,10 @@ /** * Flags a test that requires the Java Internet stack to function as expected. - * + * * This currently checks if we can bind to an unspecified (0) port on the loopback device, which * unfortunately may not always succeed. In the failure case, the annotated tests will be skipped. - * + * * @author Christian Kohlschütter */ @Target({ElementType.TYPE, ElementType.METHOD}) diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/java/SelectorTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/SelectorTest.java index 2186a9653..85ebb8376 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/java/SelectorTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/SelectorTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/java/SoTimeoutTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/SoTimeoutTest.java new file mode 100644 index 000000000..7377110be --- /dev/null +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/SoTimeoutTest.java @@ -0,0 +1,34 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.java; + +import java.net.InetSocketAddress; + +import org.newsclub.net.unix.AFSocketCapability; +import org.newsclub.net.unix.AFSocketCapabilityRequirement; + +import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; + +@AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DOMAIN) +@SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_SUPERCLASS") +public final class SoTimeoutTest extends org.newsclub.net.unix.SoTimeoutTest { + + public SoTimeoutTest() { + super(JavaAddressSpecifics.INSTANCE); + } +} diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/java/SocketChannelTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/SocketChannelTest.java new file mode 100644 index 000000000..418316e2e --- /dev/null +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/SocketChannelTest.java @@ -0,0 +1,35 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.java; + +import java.net.InetSocketAddress; + +import org.newsclub.net.unix.AFSocketCapability; +import org.newsclub.net.unix.AFSocketCapabilityRequirement; + +import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; + +@AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DOMAIN) +@SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_SUPERCLASS") +public final class SocketChannelTest extends + org.newsclub.net.unix.SocketChannelTest { + + public SocketChannelTest() { + super(JavaAddressSpecifics.INSTANCE); + } +} diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/java/ThroughputTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/ThroughputTest.java index cd35763dc..9a822e914 100644 --- a/junixsocket-common/src/test/java/org/newsclub/net/unix/java/ThroughputTest.java +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/java/ThroughputTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,12 +20,9 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assumptions.assumeTrue; -import java.lang.reflect.InvocationTargetException; import java.net.InetAddress; import java.net.InetSocketAddress; -import java.net.ProtocolFamily; import java.net.SocketAddress; -import java.net.StandardProtocolFamily; import java.nio.channels.DatagramChannel; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; @@ -34,7 +31,6 @@ import org.junit.jupiter.api.Test; import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; -import com.kohlschutter.testutil.AvailabilityRequirement; import com.kohlschutter.testutil.SystemPropertyRequirement; @SuppressFBWarnings({ @@ -47,52 +43,6 @@ public ThroughputTest() { super(JavaAddressSpecifics.INSTANCE); } - @Test - @AvailabilityRequirement(classes = {"java.net.UnixDomainSocketAddress"}, // - message = "This test requires Java 16 or later") - public void testJEP380() throws Exception { - assumeTrue(ENABLED > 0, "Throughput tests are disabled"); - assumeTrue(PAYLOAD_SIZE > 0, "Payload must be positive"); - runTestJEP380(false); - } - - @Test - @AvailabilityRequirement(classes = {"java.net.UnixDomainSocketAddress"}, // - message = "This test requires Java 16 or later") - public void testJEP380directBuffer() throws Exception { - assumeTrue(ENABLED > 0, "Throughput tests are disabled"); - assumeTrue(PAYLOAD_SIZE > 0, "Payload must be positive"); - runTestJEP380(true); - } - - private static SocketAddress jep380SocketAddress(String path) throws IllegalAccessException, - IllegalArgumentException, InvocationTargetException, SecurityException { - try { - // We use reflection so we can compile on older Java versions - Class klazz = Class.forName("java.net.UnixDomainSocketAddress"); - return (SocketAddress) klazz.getMethod("of", String.class).invoke(null, path); - } catch (NoSuchMethodException | ClassNotFoundException e) { - assumeTrue(false, "java.net.UnixDomainSocketAddress (JEP 380) not supported by JVM"); - return null; - } - } - - private void runTestJEP380(boolean direct) throws Exception { - SocketAddress sa = jep380SocketAddress(socketFile().getPath()); - - ServerSocketChannel ssc; - // We use reflection so we can compile on older Java versions - try { - ssc = (ServerSocketChannel) ServerSocketChannel.class.getMethod("open", ProtocolFamily.class) - .invoke(null, StandardProtocolFamily.valueOf("UNIX")); - } catch (Exception e) { - throw new IllegalStateException(e); - } - - runTestSocketChannel("JEP380 SocketChannel", sa, ssc, () -> SocketChannel.open(ssc - .getLocalAddress()), direct); - } - @Test @SystemPropertyRequirement(property = "org.newsclub.net.unix.throughput-test.ip.enabled", // value = "1", message = "Loopback TCP/IP testing is disabled") @@ -114,10 +64,10 @@ public void testTCPLoopbackDirectBuffer() throws Exception { private void runTestTCPLoopback(boolean direct) throws Exception { final SocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); - ServerSocketChannel ssc = ServerSocketChannel.open(); - - runTestSocketChannel("TCP-Loopback", sa, ssc, () -> SocketChannel.open(ssc.getLocalAddress()), - direct); + try (ServerSocketChannel ssc = ServerSocketChannel.open(); + SocketChannel sc = SocketChannel.open(ssc.getLocalAddress())) { + runTestSocketChannel("TCP-Loopback", sa, ssc, () -> sc, direct); + } } @Test diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/jep380/InterruptIssue158Test.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/jep380/InterruptIssue158Test.java new file mode 100644 index 000000000..f77502241 --- /dev/null +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/jep380/InterruptIssue158Test.java @@ -0,0 +1,56 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.jep380; + +import java.io.FileNotFoundException; +import java.net.SocketAddress; +import java.net.SocketException; + +import org.newsclub.net.unix.AFUNIXSocketAddress; + +import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; +import com.kohlschutter.testutil.AvailabilityRequirement; + +/** + * Test interrupt-related behavior, as discussed in + * issue 158. + * + * @author https://github.com/cenodis + * @author Christian Kohlschütter + */ +@AvailabilityRequirement(classes = "java.net.UnixDomainSocketAddress", // + message = "This test requires Java 16 or later") +@SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_SUPERCLASS") +public class InterruptIssue158Test extends + org.newsclub.net.unix.InterruptIssue158Test { + + public InterruptIssue158Test() { + super(JEP380AddressSpecifics.INSTANCE); + } + + @Override + protected void deleteSocketFile(SocketAddress sa) { + try { + AFUNIXSocketAddress.unwrap(sa).getFile().delete(); + } catch (FileNotFoundException ignore) { + // ignore + } catch (SocketException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/jep380/JEP380AddressSpecifics.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/jep380/JEP380AddressSpecifics.java new file mode 100644 index 000000000..e64802f7a --- /dev/null +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/jep380/JEP380AddressSpecifics.java @@ -0,0 +1,254 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.jep380; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.ProtocolFamily; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.StandardProtocolFamily; +import java.nio.channels.DatagramChannel; +import java.nio.channels.Pipe; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.channels.spi.AbstractSelector; +import java.nio.channels.spi.SelectorProvider; +import java.nio.file.Path; + +import org.newsclub.net.unix.AFUNIXSocketAddress; +import org.newsclub.net.unix.AddressSpecifics; +import org.newsclub.net.unix.CloseablePair; + +import com.kohlschutter.testutil.TestAbortedNotAnIssueException; + +public final class JEP380AddressSpecifics implements AddressSpecifics { + public static final AddressSpecifics INSTANCE = new JEP380AddressSpecifics(); + + private final ProtocolFamily pf; + private final Method addressOfMethod; + private final Method socketChannelOpenMethod; + private final Method serverSocketChannelOpenMethod; + private final Method datagramChannelOpenMethod; + + private final SelectorProvider sp; + + private JEP380AddressSpecifics() { + this.pf = unixProtocolFamilyIfAvailable(); + this.addressOfMethod = tryResolve("java.net.UnixDomainSocketAddress", "of", Path.class); + this.socketChannelOpenMethod = tryResolve("java.nio.channels.SocketChannel", "open", + ProtocolFamily.class); + this.serverSocketChannelOpenMethod = tryResolve("java.nio.channels.ServerSocketChannel", "open", + ProtocolFamily.class); + this.datagramChannelOpenMethod = tryResolve("java.nio.channels.DatagramChannel", "open", + ProtocolFamily.class); + + this.sp = new SelectorProvider() { + private final SelectorProvider upstream = SelectorProvider.provider(); + + @Override + public SocketChannel openSocketChannel() throws IOException { + return newSocketChannel(); + } + + @Override + public ServerSocketChannel openServerSocketChannel() throws IOException { + return newServerSocketChannel(); + } + + @Override + public AbstractSelector openSelector() throws IOException { + return upstream.openSelector(); + } + + @Override + public Pipe openPipe() throws IOException { + return upstream.openPipe(); + } + + @Override + public DatagramChannel openDatagramChannel(ProtocolFamily family) throws IOException { + return upstream.openDatagramChannel(family); + } + + @Override + public DatagramChannel openDatagramChannel() throws IOException { + return newDatagramChannel(); + } + }; + } + + private static Method tryResolve(String className, String methodName, Class... argType) { + try { + Class udsa = Class.forName(className); + return udsa.getMethod(methodName, argType); + } catch (Exception e) { + return null; + } + } + + private ProtocolFamily unixProtocolFamily() { + if (pf == null) { + throw new UnsupportedOperationException("StandardProtocolFamily.UNIX is unavailable"); + } + return pf; + } + + public static ProtocolFamily unixProtocolFamilyIfAvailable() { + for (ProtocolFamily pf : StandardProtocolFamily.values()) { + if ("UNIX".equals(pf.name())) { + return pf; + } + } + return null; + } + + private SocketAddress addressOfPath(Path p) { + if (addressOfMethod == null) { + throw new UnsupportedOperationException("UnixDomainSocketAddress.of is unavailable"); + } + try { + return (SocketAddress) addressOfMethod.invoke(null, p); + } catch (Exception e) { + throw new UnsupportedOperationException("UnixDomainSocketAddress.of is unavailable", e); + } + } + + public SocketAddress wildcardBindAddress() throws IOException { + AFUNIXSocketAddress tmpAddr = AFUNIXSocketAddress.ofNewTempFile(); + return addressOfPath(tmpAddr.getFile().toPath()); + } + + @Override + public SocketAddress newTempAddress() throws IOException { + return wildcardBindAddress(); + } + + @Override + public Socket newStrictSocket() throws IOException { + return newSocket(); + } + + @Override + public Socket newSocket() throws IOException { + throw new TestAbortedNotAnIssueException("unsupported by Java SDK Unix Domain sockets"); + } + + @Override + public DatagramSocket newDatagramSocket() throws IOException { + throw new TestAbortedNotAnIssueException("unsupported by Java SDK Unix Domain sockets"); + } + + @Override + public ServerSocket newServerSocket() throws IOException { + throw new TestAbortedNotAnIssueException("unsupported by Java SDK Unix Domain sockets"); + } + + @Override + public SocketAddress newTempAddressForDatagram() throws IOException { + return newTempAddress(); + } + + @Override + public SocketAddress unwrap(InetAddress addr, int port) throws SocketException { + throw new TestAbortedNotAnIssueException("unsupported by Java SDK Unix Domain sockets"); + } + + @Override + public SelectorProvider selectorProvider() { + return sp; + } + + @Override + public CloseablePair newSocketPair() throws IOException { + CloseablePair p1 = newInterconnectedSockets(); + return new CloseablePair<>(p1.getFirst().getChannel(), p1.getSecond().getChannel(), p1); + } + + @Override + public CloseablePair newDatagramSocketPair() throws IOException { + DatagramChannel ds1 = newDatagramChannel(); + ds1.bind(newTempAddress()); + DatagramChannel ds2 = newDatagramChannel(); + ds2.bind(newTempAddress()); + + ds1.connect(ds2.getLocalAddress()); + ds2.connect(ds1.getLocalAddress()); + return new CloseablePair<>(ds1, ds2, () -> { + ds1.close(); + ds2.close(); + }); + } + + @Override + public ServerSocket newServerSocketBindOn(SocketAddress addr) throws IOException { + throw new TestAbortedNotAnIssueException("unsupported by Java SDK Unix Domain sockets"); + } + + @Override + public Socket connectTo(SocketAddress addr) throws IOException { + throw new TestAbortedNotAnIssueException("unsupported by Java SDK Unix Domain sockets"); + } + + @Override + public ServerSocket newServerSocketBindOn(SocketAddress addr, boolean deleteOnClose) + throws IOException { + throw new TestAbortedNotAnIssueException("unsupported by Java SDK Unix Domain sockets"); + } + + @Override + public String addressFamilyString() { + return "Java UNIX domain socket"; + } + + @Override + public String summaryImportantMessage(String message) { + return message; + } + + @Override + public SocketChannel newSocketChannel() throws IOException { + try { + return (SocketChannel) socketChannelOpenMethod.invoke(null, unixProtocolFamily()); + } catch (Exception e) { + throw new UnsupportedOperationException(e); + } + } + + @Override + public DatagramChannel newDatagramChannel() throws IOException { + try { + return (DatagramChannel) datagramChannelOpenMethod.invoke(null, unixProtocolFamily()); + } catch (Exception e) { + throw new UnsupportedOperationException(e); + } + } + + @Override + public ServerSocketChannel newServerSocketChannel() throws IOException { + try { + return (ServerSocketChannel) serverSocketChannelOpenMethod.invoke(null, unixProtocolFamily()); + } catch (Exception e) { + throw new UnsupportedOperationException(e); + } + } +} diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/jep380/SocketChannelTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/jep380/SocketChannelTest.java new file mode 100644 index 000000000..a082353a5 --- /dev/null +++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/jep380/SocketChannelTest.java @@ -0,0 +1,65 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.jep380; + +import java.io.IOException; +import java.net.SocketAddress; +import java.nio.channels.ServerSocketChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.newsclub.net.unix.AFSocketCapability; +import org.newsclub.net.unix.AFSocketCapabilityRequirement; + +import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; +import com.kohlschutter.testutil.AvailabilityRequirement; + +@AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DOMAIN) +@AvailabilityRequirement(classes = "java.net.UnixDomainSocketAddress", // + message = "This test requires Java 16 or later") +@SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_SUPERCLASS") +public final class SocketChannelTest extends + org.newsclub.net.unix.SocketChannelTest { + + public SocketChannelTest() { + super(JEP380AddressSpecifics.INSTANCE); + } + + @Override + protected boolean mayTestBindNullThrowUnsupportedOperationException() { + return false; + } + + @Override + protected boolean mayTestBindNullHaveNullLocalSocketAddress() { + return false; + } + + @Override + protected void cleanupTestBindNull(ServerSocketChannel sc, SocketAddress addr) + throws ClassNotFoundException, IOException { + if (!Class.forName("java.net.UnixDomainSocketAddress").isAssignableFrom(addr.getClass())) { + return; + } + + // JEP380 doesn't clean up socket files + Path p = Paths.get(addr.toString()); + Files.delete(p); + } +} diff --git a/junixsocket-common/src/test/java15/org/newsclub/net/unix/domain/ThroughputTestShim.java b/junixsocket-common/src/test/java15/org/newsclub/net/unix/domain/ThroughputTestShim.java new file mode 100644 index 000000000..c41accd83 --- /dev/null +++ b/junixsocket-common/src/test/java15/org/newsclub/net/unix/domain/ThroughputTestShim.java @@ -0,0 +1,56 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.domain; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; + +import org.junit.jupiter.api.Test; +import org.newsclub.net.unix.AFUNIXSocketAddress; +import org.newsclub.net.unix.AddressSpecifics; + +import com.kohlschutter.testutil.AvailabilityRequirement; + +/** + * Shim class to allow for some tests that test code that is only available in newer Java versions, + * particularly JEP 380-related code (UnixDomainSocketAddress). + * + * @author Christian Kohlschütter + */ +abstract class ThroughputTestShim extends + org.newsclub.net.unix.ThroughputTest { + + protected ThroughputTestShim(AddressSpecifics asp) { + super(asp); + } + + @Test + @AvailabilityRequirement(classes = "java.net.UnixDomainSocketAddress", // + message = "This test requires Java 16 or later") + public void testJEP380() throws Exception { + fail("Should not be reached"); + } + + @Test + @AvailabilityRequirement(classes = "java.net.UnixDomainSocketAddress", // + message = "This test requires Java 16 or later") + public void testJEP380direct() throws Exception { + fail("Should not be reached"); + } +} diff --git a/junixsocket-common/src/test/java15/org/newsclub/net/unix/domain/UnixDomainSocketAddressTest.java b/junixsocket-common/src/test/java15/org/newsclub/net/unix/domain/UnixDomainSocketAddressTest.java new file mode 100644 index 000000000..f425e9df8 --- /dev/null +++ b/junixsocket-common/src/test/java15/org/newsclub/net/unix/domain/UnixDomainSocketAddressTest.java @@ -0,0 +1,41 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.domain; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; + +import org.junit.jupiter.api.Test; +import org.newsclub.net.unix.AFUNIXSocketAddress; +import org.newsclub.net.unix.SocketTestBase; + +import com.kohlschutter.testutil.AvailabilityRequirement; + +public class UnixDomainSocketAddressTest extends SocketTestBase { + public UnixDomainSocketAddressTest() { + super(AFUNIXAddressSpecifics.INSTANCE); + } + + @Test + @AvailabilityRequirement(classes = "java.net.UnixDomainSocketAddress", // + message = "This test requires Java 16 or later") + public void testConvertUnixDomainSocketAddress() throws Exception { + fail("Should not be reached"); + } +} diff --git a/junixsocket-common/src/test/java8/org/newsclub/net/unix/FinalizeTest.java b/junixsocket-common/src/test/java8/org/newsclub/net/unix/FinalizeTest.java index 7fdfabd30..1f0506a98 100644 --- a/junixsocket-common/src/test/java8/org/newsclub/net/unix/FinalizeTest.java +++ b/junixsocket-common/src/test/java8/org/newsclub/net/unix/FinalizeTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2021 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ /** * Not supported on Java 8 yet, but you can manually verify that it works by running the following * commands from three different shells: - * + * *
    *
  1. (shell 1) {@code nc -l -U /tmp/testsocket}
  2. *
  3. (shell 2) {@code java -Dtest.junixsocket.socket=/tmp/testsocket @@ -37,10 +37,10 @@ *
  4. (shell 1) type some text, followed by enter
  5. *
  6. (shell 3) {@code lsof -U -a -p $(jps | grep FinalizeTestClient | cut -f 1 -d' ') | wc -l} *
- * + * * The number printed in shell 3 after the second {@code lsof} commands should be one less than the * one of the first {@code lsof} command. - * + * * See issue 29 for details. */ public abstract class FinalizeTest extends SocketTestBase { diff --git a/junixsocket-common/src/test/java8/org/newsclub/net/unix/StandardSocketOptionsTest.java b/junixsocket-common/src/test/java8/org/newsclub/net/unix/StandardSocketOptionsTest.java index d0e9448ef..d37c93db9 100644 --- a/junixsocket-common/src/test/java8/org/newsclub/net/unix/StandardSocketOptionsTest.java +++ b/junixsocket-common/src/test/java8/org/newsclub/net/unix/StandardSocketOptionsTest.java @@ -1,3 +1,20 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.newsclub.net.unix; import java.net.SocketAddress; @@ -9,10 +26,10 @@ /** * Tests the {@code Socket#getOption(SocketOption)} API available since Java 9. - * + * * This class (in src/test/java8) is a stub that overrides this type so we can compile for Java 8 * and, at the same time, acknowledge the absence of the test programmatically in jUnit. - * + * * @author Christian Kohlschütter */ public abstract class StandardSocketOptionsTest extends SocketTestBase { diff --git a/junixsocket-common/src/test/java8/org/newsclub/net/unix/domain/FinalizeTest.java b/junixsocket-common/src/test/java8/org/newsclub/net/unix/domain/FinalizeTest.java index a070d3d8f..01a8925c6 100644 --- a/junixsocket-common/src/test/java8/org/newsclub/net/unix/domain/FinalizeTest.java +++ b/junixsocket-common/src/test/java8/org/newsclub/net/unix/domain/FinalizeTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-core/pom.xml b/junixsocket-core/pom.xml index e588428e0..c828f6e4b 100644 --- a/junixsocket-core/pom.xml +++ b/junixsocket-core/pom.xml @@ -7,7 +7,7 @@ com.kohlschutter.junixsocket junixsocket - 2.6.1 + 2.10.1 ../pom.xml junixsocket-core @@ -26,12 +26,10 @@ com.kohlschutter.junixsocket junixsocket-native-common - ${project.version} com.kohlschutter.junixsocket junixsocket-common - ${project.version} diff --git a/junixsocket-core/src/site/site.xml b/junixsocket-core/src/site/site.xml index 873972284..6d13afab6 100644 --- a/junixsocket-core/src/site/site.xml +++ b/junixsocket-core/src/site/site.xml @@ -7,6 +7,7 @@ org.apache.maven.skins maven-fluido-skin + 2.0.0-M8 diff --git a/junixsocket-darwin/README.md b/junixsocket-darwin/README.md new file mode 100644 index 000000000..e6d265c1a --- /dev/null +++ b/junixsocket-darwin/README.md @@ -0,0 +1,51 @@ +# junixsocket-darwin + +Darwin (macOS)-specific sockets from Java! + +[junixsocket](https://kohlschutter.github.io/junixsocket/) is an Apache 2.0-licensed Java/JNI library that allows the use of +[Unix Domain Sockets](https://en.wikipedia.org/wiki/Unix_domain_socket) (AF_UNIX sockets), and +other address/protocol families (such as [TIPC](http://tipc.io/)), from Java. + +## AF_SYSTEM + +`AF_SYSTEM` is one way to communicate with the macOS (Darwin) Kernel from userspace. + +Most often, you still have to be root to connect, however you may of course send a file descriptor +to a lesser privileged process and continue the work there. + +One feature junixsocket exposes is `UTUN_CONTROL`, which lets you set up PtP tunnel interfaces (`utun`) +from userspace. This can be used to implement VPNs, for example. AF_SYSTEM with UTUN_CONTROL is +the macOS pendant to Linux's `tun` functionality. + +### Examples + +* [UtunTest — Run a VPN tunnel from Java](https://kohlschutter.github.io/junixsocket/junixsocket-darwin/xref-test/org/newsclub/net/unix/darwin/system/UtunTest.html) +* Also see [junixsocket-darwin's unit tests](https://kohlschutter.github.io/junixsocket/junixsocket-darwin/xref-test/index.html) + +## AF_NDRV + +This is pretty much a "Fake Ethernet" driver; it's the macOS pendant to Linux's `tap` functionality. + +A future version of junixsocket may add support for this domain. + +## AF_MULTIPATH + +This is an Apple-private domain to implement Multipath-TCP/IP. + +A future version of junixsocket may add support for this domain. + +## Maven dependencies + +Add the following dependency to your project (replace X.Y.Z. with the +[latest junixsocket version](https://kohlschutter.github.io/junixsocket/changelog.html)). + + + com.kohlschutter.junixsocket + junixsocket-darwin + X.Y.Z + + +## References + +* [New OSX Book Volume 1 Chapter 16 — Нет-Work: Darwin Networking](http://newosxbook.com/bonus/vol1ch16.html) +* [XNU kernel source](https://github.com/apple-oss-distributions/xnu) diff --git a/junixsocket-darwin/pom.xml b/junixsocket-darwin/pom.xml new file mode 100644 index 000000000..51593cb5e --- /dev/null +++ b/junixsocket-darwin/pom.xml @@ -0,0 +1,97 @@ + + + 4.0.0 + junixsocket-darwin + jar + + com.kohlschutter.junixsocket + junixsocket + 2.10.1 + ../pom.xml + + junixsocket-darwin + + ${project.parent.basedir} + + 8 + + org.newsclub.net.unix=${project.basedir}/../junixsocket-common/${kohlschutter.target.name}/test-classes:${settings.localRepository}/com/kohlschutter/junixsocket/junixsocket-common/${project.version}/junixsocket-common-${project.version}-tests.jar + + + + --patch-module ${patchArgs.org.newsclub.net.unix} + + + The junixsocket implementation for AF_SYSTEM sockets + + + + com.kohlschutter.junixsocket + junixsocket-common + test-jar + tests + test + + + + com.kohlschutter.junixsocket + junixsocket-common + + + com.kohlschutter.junixsocket + junixsocket-core + pom + + + + com.kohlschutter.junixsocket + junixsocket-native-common + test + + + + + + maven-compiler-plugin + + + default-testCompile + test-compile + + + --patch-module + ${patchArgs.org.newsclub.net.unix} + + ${xlint.compiler.arg} + + + + + + + + + + + with-native-custom + + + !junixsocket.native-custom.skip + + + + + com.kohlschutter.junixsocket + junixsocket-native-custom + ${project.version} + test + default + true + + + + + diff --git a/junixsocket-darwin/src/main/java/module-info.java b/junixsocket-darwin/src/main/java/module-info.java new file mode 100644 index 000000000..6ae4e53de --- /dev/null +++ b/junixsocket-darwin/src/main/java/module-info.java @@ -0,0 +1,12 @@ +/** + * The junixsocket implementation for things specific to Darwin (macOS kernel), such as AF_SYSTEM + * sockets. + */ +module org.newsclub.net.unix.darwin { + exports org.newsclub.net.unix.darwin.system; + + requires transitive org.newsclub.net.unix; + + requires static com.kohlschutter.annotations.compiletime; + requires static org.eclipse.jdt.annotation; +} diff --git a/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/package-info.java b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/package-info.java new file mode 100644 index 000000000..59282e23b --- /dev/null +++ b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/package-info.java @@ -0,0 +1,4 @@ +/** + * The junixsocket implementation for Darwin (macOS Kernel)-specific sockets. + */ +package org.newsclub.net.unix.darwin; diff --git a/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMDatagramChannel.java b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMDatagramChannel.java new file mode 100644 index 000000000..68bd8d5da --- /dev/null +++ b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMDatagramChannel.java @@ -0,0 +1,60 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin.system; + +import java.io.IOException; +import java.net.ProtocolFamily; +import java.nio.channels.DatagramChannel; + +import org.newsclub.net.unix.AFDatagramChannel; +import org.newsclub.net.unix.AFSYSTEMSocketAddress; + +/** + * A {@link DatagramChannel} implementation that works with {@code AF_SYSTEM} sockets. + * + * @author Christian Kohlschütter + */ +public final class AFSYSTEMDatagramChannel extends AFDatagramChannel + implements AFSYSTEMSocketExtensions { + AFSYSTEMDatagramChannel(AFSYSTEMDatagramSocket socket) { + super(AFSYSTEMSelectorProvider.getInstance(), socket); + } + + /** + * Opens a datagram channel. + * + * @return The new channel + * @throws IOException if an I/O error occurs + */ + public static AFSYSTEMDatagramChannel open() throws IOException { + return AFSYSTEMSelectorProvider.provider().openDatagramChannel(); + } + + /** + * Opens a datagram channel. + * + * @param family The protocol family + * @return A new datagram channel + * + * @throws UnsupportedOperationException if the specified protocol family is not supported + * @throws IOException if an I/O error occurs + */ + public static AFSYSTEMDatagramChannel open(ProtocolFamily family) throws IOException { + return AFSYSTEMSelectorProvider.provider().openDatagramChannel(family); + } +} diff --git a/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMDatagramSocket.java b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMDatagramSocket.java new file mode 100644 index 000000000..0a653072e --- /dev/null +++ b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMDatagramSocket.java @@ -0,0 +1,116 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin.system; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.DatagramSocket; + +import org.newsclub.net.unix.AFDatagramSocket; +import org.newsclub.net.unix.AFSYSTEMSocketAddress; +import org.newsclub.net.unix.AFSYSTEMSocketImplExtensions; +import org.newsclub.net.unix.AFSocketType; + +/** + * A {@link DatagramSocket} implementation that works with {@code AF_SYSTEM} sockets. + * + * @author Christian Kohlschütter + */ +public final class AFSYSTEMDatagramSocket extends AFDatagramSocket implements + AFSYSTEMSocketExtensions { + + AFSYSTEMDatagramSocket(FileDescriptor fd) throws IOException { + this(fd, AFSocketType.SOCK_DGRAM); + } + + AFSYSTEMDatagramSocket(FileDescriptor fd, AFSocketType socketType) throws IOException { + super(new AFSYSTEMDatagramSocketImpl(fd, socketType)); + } + + @Override + protected AFSYSTEMDatagramChannel newChannel() { + return new AFSYSTEMDatagramChannel(this); + } + + /** + * Returns a new {@link AFSYSTEMDatagramSocket} instance, using the default + * {@link AFSocketType#SOCK_DGRAM} socket type. + * + * @return The new instance. + * @throws IOException on error. + */ + public static AFSYSTEMDatagramSocket newInstance() throws IOException { + return (AFSYSTEMDatagramSocket) newInstance(AFSYSTEMDatagramSocket::new); + } + + /** + * Returns a new {@link AFSYSTEMDatagramSocket} instance for the given socket type. + * + * @param socketType The socket type. + * @return The new instance. + * @throws IOException on error. + */ + public static AFSYSTEMDatagramSocket newInstance(AFSocketType socketType) throws IOException { + return (AFSYSTEMDatagramSocket) newInstance((fd) -> { + return new AFSYSTEMDatagramSocket(fd, socketType); + }); + } + + @Override + public AFSYSTEMDatagramChannel getChannel() { + return (AFSYSTEMDatagramChannel) super.getChannel(); + } + + @Override + protected AFSYSTEMSocketImplExtensions getImplExtensions() { + return (AFSYSTEMSocketImplExtensions) super.getImplExtensions(); + } + + /** + * Retrieves the kernel control ID given a kernel control name. + * + * An {@link IOException} is thrown if the ID is invalid or could not be accessed due to access + * restrictions. + * + * @param name The control name + * @return The control Id. + * @throws IOException on error. + */ + public int getNodeIdentity(String name) throws IOException { + return getImplExtensions().getKernelControlId(getFileDescriptor(), name); + } + + /** + * Retrieves the kernel control ID given a kernel control name. + * + * An {@link IOException} is thrown if the ID is invalid or could not be accessed due to access + * restrictions. + * + * @param name The control name + * @return The control Id. + * @throws IOException on error. + */ + public int getNodeIdentity(WellKnownKernelControlNames name) throws IOException { + return getNodeIdentity(name.getControlName()); + } + + @Override + protected AFDatagramSocket newDatagramSocketInstance() throws IOException { + return new AFSYSTEMDatagramSocket(null); + } +} diff --git a/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMDatagramSocketImpl.java b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMDatagramSocketImpl.java new file mode 100644 index 000000000..69966b30c --- /dev/null +++ b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMDatagramSocketImpl.java @@ -0,0 +1,31 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin.system; + +import java.io.FileDescriptor; +import java.io.IOException; + +import org.newsclub.net.unix.AFDatagramSocketImpl; +import org.newsclub.net.unix.AFSYSTEMSocketAddress; +import org.newsclub.net.unix.AFSocketType; + +final class AFSYSTEMDatagramSocketImpl extends AFDatagramSocketImpl { + AFSYSTEMDatagramSocketImpl(FileDescriptor fd, AFSocketType socketType) throws IOException { + super(AFSYSTEMSelectorProvider.AF_SYSTEM, fd, socketType); + } +} diff --git a/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMProtocolFamily.java b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMProtocolFamily.java new file mode 100644 index 000000000..cb9e884cf --- /dev/null +++ b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMProtocolFamily.java @@ -0,0 +1,49 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin.system; + +import java.io.IOException; + +import org.newsclub.net.unix.AFProtocolFamily; + +/** + * Describes the protocol families supported by junixsocket-afsystem. + * + * @author Christian Kohlschütter + */ +public enum AFSYSTEMProtocolFamily implements AFProtocolFamily { + /** + * SYSTEM. + */ + SYSTEM; + + @Override + public AFSYSTEMDatagramChannel openDatagramChannel() throws IOException { + return AFSYSTEMDatagramChannel.open(); + } + + @Override + public AFSYSTEMServerSocketChannel openServerSocketChannel() throws IOException { + return AFSYSTEMServerSocketChannel.open(); + } + + @Override + public AFSYSTEMSocketChannel openSocketChannel() throws IOException { + return AFSYSTEMSocketChannel.open(); + } +} diff --git a/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSelectorProvider.java b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSelectorProvider.java new file mode 100644 index 000000000..3d27c02aa --- /dev/null +++ b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSelectorProvider.java @@ -0,0 +1,201 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin.system; + +import java.io.IOException; +import java.net.ProtocolFamily; +import java.net.SocketAddress; + +import org.eclipse.jdt.annotation.NonNull; +import org.newsclub.net.unix.AFAddressFamily; +import org.newsclub.net.unix.AFAddressFamilyConfig; +import org.newsclub.net.unix.AFDatagramChannel; +import org.newsclub.net.unix.AFDatagramSocket; +import org.newsclub.net.unix.AFSYSTEMSocketAddress; +import org.newsclub.net.unix.AFSelectorProvider; +import org.newsclub.net.unix.AFServerSocket; +import org.newsclub.net.unix.AFServerSocketChannel; +import org.newsclub.net.unix.AFSocket; +import org.newsclub.net.unix.AFSocketChannel; +import org.newsclub.net.unix.AFSocketPair; +import org.newsclub.net.unix.AFSocketType; +import org.newsclub.net.unix.AFSomeSocket; + +import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; + +/** + * Service-provider class for junixsocket selectors and selectable channels. + */ +public final class AFSYSTEMSelectorProvider extends AFSelectorProvider { + private static final AFSYSTEMSelectorProvider INSTANCE = new AFSYSTEMSelectorProvider(); + + @SuppressWarnings("null") + static final AFAddressFamily<@NonNull AFSYSTEMSocketAddress> AF_SYSTEM = AFAddressFamily + .registerAddressFamilyImpl("system", AFSYSTEMSocketAddress.addressFamily(), + new AFAddressFamilyConfig() { + + @Override + protected Class> socketClass() { + return AFSYSTEMSocket.class; + } + + @Override + protected AFSocket.Constructor socketConstructor() { + return AFSYSTEMSocket::new; + } + + @Override + protected Class> serverSocketClass() { + return AFSYSTEMServerSocket.class; + } + + @Override + protected AFServerSocket.Constructor serverSocketConstructor() { + return AFSYSTEMServerSocket::new; + } + + @Override + protected Class> socketChannelClass() { + return AFSYSTEMSocketChannel.class; + } + + @Override + protected Class> serverSocketChannelClass() { + return AFSYSTEMServerSocketChannel.class; + } + + @Override + protected Class> datagramSocketClass() { + return AFSYSTEMDatagramSocket.class; + } + + @Override + protected AFDatagramSocket.Constructor datagramSocketConstructor() { + return AFSYSTEMDatagramSocket::new; + } + + @Override + protected Class> datagramChannelClass() { + return AFSYSTEMDatagramChannel.class; + } + }); + + private AFSYSTEMSelectorProvider() { + super(); + } + + /** + * Returns the singleton instance. + * + * @return The instance. + */ + @SuppressFBWarnings("MS_EXPOSE_REP") + public static AFSYSTEMSelectorProvider getInstance() { + return INSTANCE; + } + + /** + * Returns the singleton instance. + * + * @return The instance. + */ + public static AFSYSTEMSelectorProvider provider() { + return getInstance(); + } + + /** + * Constructs a new socket pair from two sockets. + * + * @param s1 Some socket, the first one. + * @param s2 Some socket, the second one. + * @return The pair. + */ + @Override + protected

AFSocketPair

newSocketPair(P s1, P s2) { + return new AFSYSTEMSocketPair<>(s1, s2); + } + + @SuppressWarnings("unchecked") + @Override + public AFSYSTEMSocketPair openSocketChannelPair() throws IOException { + return (AFSYSTEMSocketPair) super.openSocketChannelPair(); + } + + @SuppressWarnings("unchecked") + @Override + public AFSYSTEMSocketPair openDatagramChannelPair() throws IOException { + return (AFSYSTEMSocketPair) super.openDatagramChannelPair(); + } + + @SuppressWarnings("unchecked") + @Override + public AFSYSTEMSocketPair openDatagramChannelPair(AFSocketType type) + throws IOException { + return (AFSYSTEMSocketPair) super.openDatagramChannelPair(type); + } + + @Override + protected AFSYSTEMSocket newSocket() throws IOException { + return AFSYSTEMSocket.newInstance(); + } + + @Override + public AFSYSTEMDatagramChannel openDatagramChannel() throws IOException { + return AFSYSTEMDatagramSocket.newInstance().getChannel(); + } + + @Override + public AFSYSTEMDatagramChannel openDatagramChannel(AFSocketType type) throws IOException { + return AFSYSTEMDatagramSocket.newInstance(type).getChannel(); + } + + @Override + public AFSYSTEMDatagramChannel openDatagramChannel(ProtocolFamily family) throws IOException { + return (AFSYSTEMDatagramChannel) super.openDatagramChannel(family); + } + + @Override + public AFSYSTEMServerSocketChannel openServerSocketChannel() throws IOException { + return AFSYSTEMServerSocket.newInstance().getChannel(); + } + + @Override + public AFSYSTEMServerSocketChannel openServerSocketChannel(SocketAddress sa) throws IOException { + return AFSYSTEMServerSocket.bindOn(AFSYSTEMSocketAddress.unwrap(sa)).getChannel(); + } + + @Override + public AFSYSTEMSocketChannel openSocketChannel() throws IOException { + return (AFSYSTEMSocketChannel) super.openSocketChannel(); + } + + @Override + public AFSYSTEMSocketChannel openSocketChannel(SocketAddress sa) throws IOException { + return AFSYSTEMSocket.connectTo(AFSYSTEMSocketAddress.unwrap(sa)).getChannel(); + } + + @Override + protected ProtocolFamily protocolFamily() { + return AFSYSTEMProtocolFamily.SYSTEM; + } + + @Override + protected AFAddressFamily<@NonNull AFSYSTEMSocketAddress> addressFamily() { + return AF_SYSTEM; + } +} diff --git a/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMServerSocket.java b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMServerSocket.java new file mode 100644 index 000000000..f9518f3d6 --- /dev/null +++ b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMServerSocket.java @@ -0,0 +1,124 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin.system; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.SocketException; + +import org.newsclub.net.unix.AFSYSTEMSocketAddress; +import org.newsclub.net.unix.AFServerSocket; +import org.newsclub.net.unix.AFServerSocketChannel; +import org.newsclub.net.unix.AFSocket; +import org.newsclub.net.unix.AFSocketAddress; +import org.newsclub.net.unix.AFSocketImpl; + +/** + * The server part of an {@code AF_SYSTEM} socket. + * + * @author Christian Kohlschütter + */ +public final class AFSYSTEMServerSocket extends AFServerSocket { + AFSYSTEMServerSocket(FileDescriptor fdObj) throws IOException { + super(fdObj); + } + + @Override + protected AFServerSocketChannel newChannel() { + return new AFSYSTEMServerSocketChannel(this); + } + + @Override + public AFSYSTEMServerSocketChannel getChannel() { + return (AFSYSTEMServerSocketChannel) super.getChannel(); + } + + /** + * Returns a new, unbound AF_SYSTEM {@link ServerSocket}. + * + * @return The new, unbound {@link AFServerSocket}. + * @throws IOException if the operation fails. + */ + public static AFSYSTEMServerSocket newInstance() throws IOException { + return (AFSYSTEMServerSocket) AFServerSocket.newInstance(AFSYSTEMServerSocket::new); + } + + static AFSYSTEMServerSocket newInstance(FileDescriptor fdObj, int localPort, int remotePort) + throws IOException { + return (AFSYSTEMServerSocket) AFServerSocket.newInstance(AFSYSTEMServerSocket::new, fdObj, + localPort, remotePort); + } + + /** + * Returns a new AF_SYSTEM {@link ServerSocket} that is bound to the given + * {@link AFSYSTEMSocketAddress}. + * + * @param addr The socket file to bind to. + * @return The new, bound {@link AFServerSocket}. + * @throws IOException if the operation fails. + */ + public static AFSYSTEMServerSocket bindOn(final AFSYSTEMSocketAddress addr) throws IOException { + return (AFSYSTEMServerSocket) AFServerSocket.bindOn(AFSYSTEMServerSocket::new, addr); + } + + /** + * Returns a new AF_SYSTEM {@link ServerSocket} that is bound to the given + * {@link AFSocketAddress}. + * + * @param addr The socket file to bind to. + * @param deleteOnClose If {@code true}, the socket file (if the address points to a file) will be + * deleted upon {@link #close}. + * @return The new, bound {@link AFServerSocket}. + * @throws IOException if the operation fails. + */ + public static AFSYSTEMServerSocket bindOn(final AFSYSTEMSocketAddress addr, boolean deleteOnClose) + throws IOException { + return (AFSYSTEMServerSocket) AFServerSocket.bindOn(AFSYSTEMServerSocket::new, addr, + deleteOnClose); + } + + /** + * Returns a new, unbound AF_SYSTEM {@link ServerSocket} that will always bind to the + * given address, regardless of any socket address used in a call to bind. + * + * @param forceAddr The address to use. + * @return The new, yet unbound {@link AFServerSocket}. + * @throws IOException if an exception occurs. + */ + public static AFSYSTEMServerSocket forceBindOn(final AFSYSTEMSocketAddress forceAddr) + throws IOException { + return (AFSYSTEMServerSocket) AFServerSocket.forceBindOn(AFSYSTEMServerSocket::new, forceAddr); + } + + @Override + protected AFSocketImpl newImpl(FileDescriptor fdObj) + throws SocketException { + return new AFSYSTEMSocketImpl(fdObj); + } + + @Override + protected AFSocket newSocketInstance() throws IOException { + return AFSYSTEMSocket.newInstance(); + } + + @Override + public AFSYSTEMSocket accept() throws IOException { + return (AFSYSTEMSocket) super.accept(); + } +} diff --git a/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMServerSocketChannel.java b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMServerSocketChannel.java new file mode 100644 index 000000000..05f3e4fa3 --- /dev/null +++ b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMServerSocketChannel.java @@ -0,0 +1,45 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin.system; + +import java.io.IOException; + +import org.newsclub.net.unix.AFSYSTEMSocketAddress; +import org.newsclub.net.unix.AFServerSocketChannel; + +/** + * A selectable channel for stream-oriented listening sockets. + * + * @author Christian Kohlschütter + */ +public final class AFSYSTEMServerSocketChannel extends + AFServerSocketChannel { + AFSYSTEMServerSocketChannel(AFSYSTEMServerSocket socket) { + super(socket, AFSYSTEMSelectorProvider.getInstance()); + } + + /** + * Opens a server-socket channel. + * + * @return The new channel + * @throws IOException on error. + */ + public static AFSYSTEMServerSocketChannel open() throws IOException { + return AFSYSTEMServerSocket.newInstance().getChannel(); + } +} diff --git a/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocket.java b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocket.java new file mode 100644 index 000000000..9a38a4b8e --- /dev/null +++ b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocket.java @@ -0,0 +1,138 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin.system; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.Socket; +import java.net.SocketException; + +import org.newsclub.net.unix.AFSYSTEMSocketAddress; +import org.newsclub.net.unix.AFSYSTEMSocketImplExtensions; +import org.newsclub.net.unix.AFSocket; +import org.newsclub.net.unix.AFSocketCapability; +import org.newsclub.net.unix.AFSocketFactory; + +/** + * Implementation of an {@code AF_SYSTEM} socket. + * + * @author Christian Kohlschütter + */ +public final class AFSYSTEMSocket extends AFSocket implements + AFSYSTEMSocketExtensions { + private static AFSYSTEMSocketImplExtensions staticExtensions = null; + + AFSYSTEMSocket(FileDescriptor fdObj, AFSocketFactory factory) + throws SocketException { + super(new AFSYSTEMSocketImpl(fdObj), factory); + } + + @SuppressWarnings("unused") + private static synchronized AFSYSTEMSocketImplExtensions getStaticImplExtensions() + throws IOException { + if (staticExtensions == null) { + try (AFSYSTEMSocket socket = new AFSYSTEMSocket(null, null)) { + staticExtensions = (AFSYSTEMSocketImplExtensions) socket.getImplExtensions(); + } + } + return staticExtensions; + } + + /** + * Returns true iff {@link AFSYSTEMSocket}s (sockets of type "AF_SYSTEM") are + * supported by the current Java VM and the kernel. + * + * To support {@link AFSYSTEMSocket}s, a custom JNI library must be loaded that is supplied with + * junixsocket, and the system must support AF_SYSTEM sockets. + * + * This call is equivalent to checking {@link AFSocket#isSupported()} and + * {@link AFSocket#supports(AFSocketCapability)} with + * {@link AFSocketCapability#CAPABILITY_DARWIN}. + * + * @return {@code true} iff supported. + */ + public static boolean isSupported() { + return AFSocket.isSupported() && AFSocket.supports(AFSocketCapability.CAPABILITY_DARWIN); + } + + @Override + protected AFSYSTEMSocketChannel newChannel() { + return new AFSYSTEMSocketChannel(this); + } + + /** + * Creates a new, unbound {@link AFSocket}. + * + * This "default" implementation is a bit "lenient" with respect to the specification. + * + * In particular, we ignore calls to {@link Socket#getTcpNoDelay()} and + * {@link Socket#setTcpNoDelay(boolean)}. + * + * @return A new, unbound socket. + * @throws IOException if the operation fails. + */ + public static AFSYSTEMSocket newInstance() throws IOException { + return (AFSYSTEMSocket) AFSocket.newInstance(AFSYSTEMSocket::new, (AFSYSTEMSocketFactory) null); + } + + static AFSYSTEMSocket newInstance(AFSYSTEMSocketFactory factory) throws SocketException { + return (AFSYSTEMSocket) AFSocket.newInstance(AFSYSTEMSocket::new, factory); + } + + /** + * Creates a new, unbound, "strict" {@link AFSocket}. + * + * This call uses an implementation that tries to be closer to the specification than + * {@link #newInstance()}, at least for some cases. + * + * @return A new, unbound socket. + * @throws IOException if the operation fails. + */ + public static AFSYSTEMSocket newStrictInstance() throws IOException { + return (AFSYSTEMSocket) AFSocket.newInstance(AFSYSTEMSocket::new, (AFSYSTEMSocketFactory) null); + } + + /** + * Creates a new {@link AFSocket} and connects it to the given {@link AFSYSTEMSocketAddress}. + * + * @param addr The address to connect to. + * @return A new, connected socket. + * @throws IOException if the operation fails. + */ + public static AFSYSTEMSocket connectTo(AFSYSTEMSocketAddress addr) throws IOException { + return (AFSYSTEMSocket) AFSocket.connectTo(AFSYSTEMSocket::new, addr); + } + + @Override + public AFSYSTEMSocketChannel getChannel() { + return (AFSYSTEMSocketChannel) super.getChannel(); + } + + /** + * Very basic self-test function. + * + * Prints "supported" and "capabilities" status to System.out. + * + * @param args ignored. + */ + public static void main(String[] args) { + System.out.print(AFSYSTEMSocket.class.getName() + ".isSupported(): "); + System.out.flush(); + System.out.println(AFSYSTEMSocket.isSupported()); + } +} diff --git a/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocketChannel.java b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocketChannel.java new file mode 100644 index 000000000..598edde7b --- /dev/null +++ b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocketChannel.java @@ -0,0 +1,45 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin.system; + +import java.io.IOException; + +import org.newsclub.net.unix.AFSYSTEMSocketAddress; +import org.newsclub.net.unix.AFSocketChannel; + +/** + * A selectable channel for stream-oriented connecting sockets. + * + * @author Christian Kohlschütter + */ +public final class AFSYSTEMSocketChannel extends AFSocketChannel implements + AFSYSTEMSocketExtensions { + AFSYSTEMSocketChannel(AFSYSTEMSocket socket) { + super(socket, AFSYSTEMSelectorProvider.getInstance()); + } + + /** + * Opens a socket channel. + * + * @return The new channel + * @throws IOException on error. + */ + public static AFSYSTEMSocketChannel open() throws IOException { + return AFSYSTEMSocket.newInstance().getChannel(); + } +} diff --git a/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocketExtensions.java b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocketExtensions.java new file mode 100644 index 000000000..676c1bccf --- /dev/null +++ b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocketExtensions.java @@ -0,0 +1,29 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin.system; + +import org.newsclub.net.unix.AFSocketExtensions; + +/** + * Defines certain methods that all junixsocket AF_SYSTEM socket implementations share and extend + * beyond the standard socket API. + * + * @author Christian Kohlschütter + */ +public interface AFSYSTEMSocketExtensions extends AFSocketExtensions { +} diff --git a/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocketFactory.java b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocketFactory.java new file mode 100644 index 000000000..01bcf4915 --- /dev/null +++ b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocketFactory.java @@ -0,0 +1,58 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin.system; + +import java.io.IOException; +import java.net.Socket; +import java.net.SocketException; + +import org.newsclub.net.unix.AFSYSTEMSocketAddress; +import org.newsclub.net.unix.AFSocketFactory; + +/** + * The base for a SocketFactory that connects to AF_SYSTEM sockets. + */ +public abstract class AFSYSTEMSocketFactory extends AFSocketFactory { + /** + * Creates a {@link AFSYSTEMSocketFactory}. + */ + protected AFSYSTEMSocketFactory() { + super(AFSYSTEMSocketAddress.class); + } + + @Override + public final Socket createSocket() throws SocketException { + return configure(AFSYSTEMSocket.newInstance(this)); + } + + @Override + protected final AFSYSTEMSocket connectTo(AFSYSTEMSocketAddress addr) throws IOException { + return configure(AFSYSTEMSocket.connectTo(addr)); + } + + /** + * Performs some optional configuration on a newly created socket. + * + * @param sock The socket. + * @return The very socket. + * @throws SocketException on error. + */ + protected AFSYSTEMSocket configure(AFSYSTEMSocket sock) throws SocketException { + return sock; + } +} diff --git a/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocketImpl.java b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocketImpl.java new file mode 100644 index 000000000..9af5e9509 --- /dev/null +++ b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocketImpl.java @@ -0,0 +1,40 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin.system; + +import java.io.FileDescriptor; +import java.net.SocketException; + +import org.newsclub.net.unix.AFSYSTEMSocketAddress; +import org.newsclub.net.unix.AFSocketImpl; + +final class AFSYSTEMSocketImpl extends AFSocketImpl { + AFSYSTEMSocketImpl(FileDescriptor fdObj) { + super(AFSYSTEMSelectorProvider.AF_SYSTEM, fdObj); + } + + @Override + public Object getOption(int optID) throws SocketException { + return getOptionLenient(optID); + } + + @Override + public void setOption(int optID, Object value) throws SocketException { + setOptionLenient(optID, value); + } +} diff --git a/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocketOptions.java b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocketOptions.java new file mode 100644 index 000000000..875c6f86f --- /dev/null +++ b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocketOptions.java @@ -0,0 +1,32 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin.system; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * AF_SYSTEM-specific socket options. + * + * @author Christian Kohlschütter + */ +@NonNullByDefault +public final class AFSYSTEMSocketOptions { + private AFSYSTEMSocketOptions() { + throw new IllegalStateException(); + } +} diff --git a/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocketPair.java b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocketPair.java new file mode 100644 index 000000000..5e0f69934 --- /dev/null +++ b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/AFSYSTEMSocketPair.java @@ -0,0 +1,61 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin.system; + +import java.io.IOException; + +import org.newsclub.net.unix.AFSocketPair; +import org.newsclub.net.unix.AFSomeSocket; + +/** + * A pair of sockets. + * + * @param The socket type. + * @author Christian Kohlschütter + */ +public final class AFSYSTEMSocketPair extends AFSocketPair { + /** + * Creates a new socket pair. + * + * @param socket1 The first socket. + * @param socket2 The second socket. + */ + AFSYSTEMSocketPair(T socket1, T socket2) { + super(socket1, socket2); + } + + /** + * Opens a socket pair of interconnected channels. + * + * @return The new channel pair. + * @throws IOException on error. + */ + public static AFSYSTEMSocketPair open() throws IOException { + return AFSYSTEMSelectorProvider.provider().openSocketChannelPair(); + } + + /** + * Opens a socket pair of interconnected datagram channels. + * + * @return The new channel pair. + * @throws IOException on error. + */ + public static AFSYSTEMSocketPair openDatagram() throws IOException { + return AFSYSTEMSelectorProvider.provider().openDatagramChannelPair(); + } +} diff --git a/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/IPUtil.java b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/IPUtil.java new file mode 100644 index 000000000..cb5e569d8 --- /dev/null +++ b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/IPUtil.java @@ -0,0 +1,173 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin.system; + +import java.nio.ByteBuffer; + +/** + * Some IP protocol-related helper methods. + * + * @author Christian Kohlschütter + */ +public final class IPUtil { + /** + * The length (in bytes) of the "domain" header used in loopback packet systems like UTUN_CONTROL. + */ + public static final int DOMAIN_HEADER_LENGTH = 4; // bytes + + /** + * The identifier for AF_INET (at least on Darwin). + */ + public static final int DOMAIN_AF_INET = 2; + + /** + * The length (in bytes) of an IPv4 header without options. + */ + public static final int IPV4_DEFAULT_HEADER_SIZE = 20; // bytes + + /** + * The ICMP protocol. + */ + public static final byte AF_INET_PROTOCOL_ICMP = 1; + + private IPUtil() { + throw new IllegalStateException("No instances"); + } + + /** + * Computes the checksum for an IPv4 header, and overwrites any existing checksum with the correct + * one. + * + * @param bb The buffer containing the IPv4 header + * @param start The beginning position of the header in the buffer. + * @param end The end position (exclusive) of the header in the buffer. + * @return The computed 16-bit checksum + */ + public static int checksumIPv4header(ByteBuffer bb, int start, int end) { + return checksumIPstyle(bb, start, end, 10); + } + + /** + * Computes the checksum for an ICMP header, and overwrites any existing checksum with the correct + * one. + * + * Also see RFC 792. + * + * @param bb The buffer containing the ICMP header + * @param start The beginning position of the header in the buffer. + * @param end The end position (exclusive) of the header in the buffer. + * @return The computed 16-bit checksum + */ + public static int checksumICMPheader(ByteBuffer bb, int start, int end) { + return checksumIPstyle(bb, start, end, 2); + } + + /** + * Computes the 16-bit checksum for some header used in IP networking, and overwrites any existing + * checksum with the correct one. + * + * Also see RFC 1071. + * + * @param bb The buffer containing the ICMP header + * @param start The beginning position of the header in the buffer. + * @param end The end position (exclusive) of the header in the buffer. + * @param checksumOffset The offset from start for an existing 16-bit checksum that is to be + * ignored. + * @return The computed 16-bit checksum + */ + private static int checksumIPstyle(ByteBuffer bb, int start, int end, int checksumOffset) { + final int checksumAt = start + checksumOffset; + int sum = 0; + + if (checksumOffset >= end) { + throw new IllegalArgumentException("checksumOffset"); + } + + // While we could pretend the checksum is 0 (by ignoring the computation at position + // checksumAt), we zero it out here, and later put the correct checksum back in. + // This should not only be faster than two for-loops or checking the position prior to adding, + // it also puts the correct checksum in place, which can come in handy when composing packets. + // It is also the recommended strategy as per RFC 1071. The downside is that we modify the + // contents of the buffer, but that's OK since we control the API. + bb.putShort(checksumAt, (short) 0); + + for (int i = start; i < end; i += 2) { + int v = bb.getShort(i) & 0xFFFF; + + sum += v; + + int overflow = (sum & ~0xFFFF); + if (overflow != 0) { + // overflow -> add carry and trim to 16-bit + sum = (sum + (overflow >>> 16)) & 0xFFFF; + } + } + + int checksum = (~sum) & 0xFFFF; + + // fix checksum + bb.putShort(checksumAt, (short) checksum); + + return checksum; + } + + /** + * Put (write) an IPv4 header to the given byte buffer, using the given parameters. + * + * This should write exactly 20 bytes to the buffer. The buffer position then is at the end of the + * header. + * + * @param bb The target byte buffer. + * @param payloadLength The length of the payload (excluding the IPv4 header). + * @param protocol The protocol identifier. + * @param srcIP The source IPv4 address. + * @param dstIP The destination IPv4 address. + */ + public static void putIPv4Header(ByteBuffer bb, int payloadLength, byte protocol, int srcIP, + int dstIP) { + bb.put((byte) 0x45); // IPv4, 5*4=20 bytes header + bb.put((byte) 0); // TOS/DSCP + bb.putShort((short) (20 + payloadLength)); // total length = header + payload + bb.putShort((short) 0); // identification + bb.putShort((short) 0); // flags and fragment offset + bb.put((byte) 65); // TTL + bb.put(protocol); // protocol (e.g., ICMP) + bb.putShort((short) 0); // header checksum (placeholder) + bb.putInt(srcIP); + bb.putInt(dstIP); + // end of header (20 bytes) + } + + /** + * Put (write) an ICMP echo response header to the given byte buffer, using the given parameters. + * + * @param bb The target byte buffer. + * @param echoIdentifier The identifier, from the ICMP echo request. + * @param sequenceNumber The sequence number, from the ICMP echo request. + * @param payload The payload, from the ICMP echo request. + */ + public static void putICMPEchoResponse(ByteBuffer bb, short echoIdentifier, short sequenceNumber, + ByteBuffer payload) { + bb.put((byte) 0); // Echo response + bb.put((byte) 0); // Echo has no other code + bb.putShort((short) 0); // ICMP checksum (placeholder) + bb.putShort(echoIdentifier); // ICMP echo identifier + bb.putShort(sequenceNumber); // ICMP echo sequence number + bb.put(payload); + } +} diff --git a/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/WellKnownKernelControlNames.java b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/WellKnownKernelControlNames.java new file mode 100644 index 000000000..dbec209b1 --- /dev/null +++ b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/WellKnownKernelControlNames.java @@ -0,0 +1,186 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin.system; + +/** + * Well-known AF_SYSTEM control names. + * + * Most of these can only be accessed by the superuser (root). Unless otherwise specified, one + * usually accesses these as datagrams (SOCK_DGRAM). + * + * You can see which control names are registered via {@code netstat -an}; check for the IDs below + * "Registered kernel control modules". + * + * Also see New OSX Book Volume 1 Chapter 16 + * + * @author Christian Kohlschütter + */ +public enum WellKnownKernelControlNames { + + /** + * {@code com.apple.netsrc}. Network/route policies and statistics. + * + * Available to non-root. + * + * See https://github.com/apple-oss-distributions/xnu/blob/main/bsd/net/netsrc.h and + * https://github.com/apple-oss-distributions/xnu/blob/main/bsd/net/netsrc.c, as well as + * https://github.com/appleopen/Libinfo/blob/master/lookup.subproj/si_compare.c + */ + NETSRC("com.apple.netsrc"), // + + /** + * {@code com.apple.network.statistics}. Live socket statistics and notifications. + * + * Available to non-root. + * + * See {@code http://newosxbook.com/src.jl?tree=listings&file=lsock.c} as well as + * https://github.com/packetzero/libntstat + */ + NETWORK_STATISTICS("com.apple.network.statistics"), // + + /** + * {@code com.apple.flow-divert}. MPTCP flow diversions (XNU-2422). + * + * See https://github.com/apple-oss-distributions/xnu/blob/main/bsd/netinet/flow_divert.c and + * https://github.com/apple-oss-distributions/xnu/blob/main/bsd/netinet/flow_divert.h + */ + FLOW_DIVERT("com.apple.flow-divert"), // + + /** + * {@code com.apple.net.netagent}. + */ + NET_NETAGENT("com.apple.net.netagent"), // + + /** + * {@code com.apple.content-filter}. + */ + CONTENT_FILTER("com.apple.content-filter"), // + + /** + * {@code com.apple.net.utun_control}. User-mode tunneling (VPN). + */ + UTUN_CONTROL("com.apple.net.utun_control"), // + + /** + * {@code com.apple.net.ipsec_control}. + */ + IPSEC_CONTROL("com.apple.net.ipsec_control"), // + + /** + * {@code com.apple.network.tcp_ccdebug}. Requires SOCK_STREAM. + */ + NETWORK_TCP_CCDEBUG("com.apple.network.tcp_ccdebug"), // + + /** + * {@code com.apple.network.advisory}. + */ + NETWORK_ADVISORY("com.apple.network.advisory"), // + + /** + * {@code com.apple.net.rvi_control}. + */ + NET_RVI_CONTROL("com.apple.net.rvi_control"), // + + /** + * {@code com.apple.nke.sockwall}. + */ + NKE_SOCKWALL("com.apple.nke.sockwall"), // + + /** + * {@code com.apple.spmi.nfc}. Available to non-root. Requires SOCK_STREAM. + */ + SPMI_NFC("com.apple.spmi.nfc"), // + + /** + * {@code com.apple.packet-mangler}. + */ + PACKET_MANGLER("com.apple.packet-mangler"), // + + /** + * {@code com.apple.net.necp_control}. + */ + NECP_CONTROL("com.apple.net.necp_control"), // + + /** + * {@code com.apple.fileutil.kext.stateful.ctl}. + */ + FILEUTIL_KEXT_STATEFUL_CTL("com.apple.fileutil.kext.stateful.ctl"), // + + /** + * {@code com.apple.fileutil.kext.stateless.ctl}. + */ + FILEUTIL_KEXT_STATELESS_CTL("com.apple.fileutil.kext.stateless.ctl"), // + + /** + * {@code com.apple.mcx.kernctl.alr}. + */ + MCX_KERNCTL_ALR("com.apple.mcx.kernctl.alr"), // + + /** + * {@code com.apple.nke.webcontentfilter}. + */ + NKE_WEBCONTENTFILTER("com.apple.nke.webcontentfilter"), // + + /** + * {@code com.apple.uart.wlan-debug}. + */ + UART_WLAN_DEBUG("com.apple.uart.wlan-debug"), // + + /** + * {@code com.apple.uart.sk.wlan-debug}. + */ + UART_SK_WLAN_DEBUG("com.apple.uart.sk.wlan-debug"), // + + /** + * {@code com.apple.uart.debug-console}. + */ + UART_DEBUG_CONSOLE("com.apple.uart.debug-console"), // + + /** + * {@code com.apple.uart.sk.debug-console}. + */ + UART_SK_DEBUG_CONSOLE("com.apple.uart.sk.debug-console"), // + + /** + * {@code com.apple.userspace_ethernet}. + */ + USERSPACE_ETHERNET("com.apple.userspace_ethernet"), // + + ; + + // non-Apple: + // - com.avira.fac + // - com.vmware.kext.vmci + // - com.vmware.kext.vmnet + // - com.vmware.kext.vmx86 + + private final String name; + + WellKnownKernelControlNames(String name) { + this.name = name; + } + + /** + * Returns the control name identifier. + * + * @return The name. + */ + public String getControlName() { + return name; + } +} diff --git a/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/package-info.java b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/package-info.java new file mode 100644 index 000000000..31e82a8ce --- /dev/null +++ b/junixsocket-darwin/src/main/java/org/newsclub/net/unix/darwin/system/package-info.java @@ -0,0 +1,4 @@ +/** + * The junixsocket implementation for Darwin AF_SYSTEM sockets. + */ +package org.newsclub.net.unix.darwin.system; diff --git a/junixsocket-darwin/src/site/site.xml b/junixsocket-darwin/src/site/site.xml new file mode 100644 index 000000000..6d13afab6 --- /dev/null +++ b/junixsocket-darwin/src/site/site.xml @@ -0,0 +1,34 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 2.0.0-M8 + + + + + kohlschutter/junixsocket + right + + + + + + ${project.name} + + + + ${project.scm.url} + + +

+ + + + diff --git a/junixsocket-darwin/src/test/java/org/newsclub/net/unix/darwin/SelftestProvider.java b/junixsocket-darwin/src/test/java/org/newsclub/net/unix/darwin/SelftestProvider.java new file mode 100644 index 000000000..b3f24e6d1 --- /dev/null +++ b/junixsocket-darwin/src/test/java/org/newsclub/net/unix/darwin/SelftestProvider.java @@ -0,0 +1,116 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin; + +import java.io.PrintWriter; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.newsclub.net.unix.darwin.system.KernelControlNamesTest; +import org.newsclub.net.unix.darwin.system.UtunTest; + +/** + * Provides references to all "junixsocket-afystem" tests that should be included in + * junixsocket-selftest. + * + * @author Christian Kohlschütter + */ +@SuppressWarnings("PMD.CouplingBetweenObjects") +public class SelftestProvider { + private static final String MODULE = "junixsocket-darwin"; + final Map>> testMap = new LinkedHashMap<>(); // NOPMD.LooseCoupling + + // CPD-OFF + + @SuppressWarnings({"PMD.ExcessiveMethodLength", "PMD.UnnecessaryFullyQualifiedName"}) + public SelftestProvider() { + registerTest(MODULE, KernelControlNamesTest.class); + registerTest(MODULE, UtunTest.class); + + // registerTest(AFSYSTEMExtensionsTest.class); + // + // registerTest(AcceptTimeoutTest.class); + // + // registerTest(AncillaryMessageTest.class); + // + // registerTest(AvailableTest.class); + // + // registerTest(BufferOverflowTest.class); + // + // registerTest(CancelAcceptTest.class); + // + // registerTest(DatagramSocketTest.class); + // + // registerTest(EndOfFileTest.class); + // + // disabled: FinalizeTest + // + // registerTest(ReadWriteTest.class); + // + // registerTest(SelectorTest.class); + // + // registerTest(ServerSocketCloseTest.class); + // + // registerTest(ServerSocketTest.class); + // + // registerTest(SocketChannelTest.class); + // + // registerTest(SocketOptionsTest.class); + // + // registerTest(SocketPairTest.class); + // + // registerTest(SocketTest.class); + // + // registerTest(SoTimeoutTest.class); + // + // registerTest(StandardSocketOptionsTest.class); + // + // registerTest(TcpNoDelayTest.class); + // + // registerTest(ThroughputTest.class); + } + + public Set modulesDisabledByDefault() { + return Collections.emptySet(); + } + + // private void registerTest(Class> test) { + // registerTest(MODULE, test); + // } + + private void registerTest(String group, Class test) { + if (test != null) { + testMap.computeIfAbsent(group, (key) -> new LinkedHashSet<>()).add(test); + } + } + + public Map[]> tests() { + Map[]> tests = new LinkedHashMap<>(); + testMap.forEach((key, set) -> { + tests.put(key, set.toArray(new Class[0])); + }); + + return tests; + } + + public void printAdditionalProperties(PrintWriter out) { + } +} diff --git a/junixsocket-darwin/src/test/java/org/newsclub/net/unix/darwin/system/IPUtilTest.java b/junixsocket-darwin/src/test/java/org/newsclub/net/unix/darwin/system/IPUtilTest.java new file mode 100644 index 000000000..62b93032d --- /dev/null +++ b/junixsocket-darwin/src/test/java/org/newsclub/net/unix/darwin/system/IPUtilTest.java @@ -0,0 +1,45 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin.system; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.ByteBuffer; + +import org.junit.jupiter.api.Test; + +public class IPUtilTest { + + @Test + public void testIPHeaderChecksum() { + // example from https://www.thegeekstuff.com/2012/05/ip-header-checksum/ + ByteBuffer bb = ByteBuffer.wrap(new byte[] { + 0x45, 0x00, // + 0x00, 0x3c, // + 0x1c, 0x46, // + 0x40, 0x00, // + 0x40, 0x06, // + 0x00, 0x00, // + (byte) 0xac, 0x10, // + 0x0a, 0x63, // + (byte) 0xac, 0x10, // + 0x0a, 0x0c}); + + assertEquals(0xB1E6, IPUtil.checksumIPv4header(bb, 0, bb.remaining())); + } +} diff --git a/junixsocket-darwin/src/test/java/org/newsclub/net/unix/darwin/system/KernelControlNamesTest.java b/junixsocket-darwin/src/test/java/org/newsclub/net/unix/darwin/system/KernelControlNamesTest.java new file mode 100644 index 000000000..d5e7e4a8f --- /dev/null +++ b/junixsocket-darwin/src/test/java/org/newsclub/net/unix/darwin/system/KernelControlNamesTest.java @@ -0,0 +1,58 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin.system; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.net.SocketException; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +public class KernelControlNamesTest { + @Test + // @AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_DARWIN) + public void testStandardKernelControlNames() throws Exception { + try (AFSYSTEMDatagramSocket socket = AFSYSTEMDatagramSocket.newInstance()) { + assertThrows(IOException.class, () -> socket.getNodeIdentity("definitely.missing")); + + int errors = 0; + Set ids = new LinkedHashSet<>(); + for (WellKnownKernelControlNames n : WellKnownKernelControlNames.values()) { + int id; + try { + id = socket.getNodeIdentity(n.getControlName()); + assertTrue(id > 0, "id should be a positive integer"); + ids.add(id); + } catch (SocketException e) { + errors++; + continue; + } + } + + System.out.println("Resolved control names " + ids.size() + "/" + WellKnownKernelControlNames + .values().length); + + assertEquals(WellKnownKernelControlNames.values().length - errors, ids.size()); + } + } +} diff --git a/junixsocket-darwin/src/test/java/org/newsclub/net/unix/darwin/system/UtunTest.java b/junixsocket-darwin/src/test/java/org/newsclub/net/unix/darwin/system/UtunTest.java new file mode 100644 index 000000000..d6fc6711c --- /dev/null +++ b/junixsocket-darwin/src/test/java/org/newsclub/net/unix/darwin/system/UtunTest.java @@ -0,0 +1,271 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.darwin.system; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import java.io.IOException; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.time.Duration; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; +import org.newsclub.net.unix.AFSYSTEMSocketAddress; +import org.newsclub.net.unix.AFSYSTEMSocketAddress.SysAddr; +import org.newsclub.net.unix.AFSocketCapability; +import org.newsclub.net.unix.AFSocketCapabilityRequirement; + +import com.kohlschutter.testutil.ExecutionEnvironmentRequirement; +import com.kohlschutter.testutil.ExecutionEnvironmentRequirement.Rule; + +/** + * Demo code to exercise AF_SYSTEM with UTUN_CONTROL. + * + * Creates a PtP VPN tunnel, sends a ping via Java SDK code, parses the ICMP echo request (ping) + * packet, and responds with a hand-crafted ICMP echo reply (pong). + * + * @author Christian Kohlschütter + */ +@SuppressWarnings("PMD.AvoidUsingHardCodedIP") +public class UtunTest { + private static final Inet4Address UTUN_SRC_IP; + private static final Inet4Address UTUN_DST_IP; + + static { + try { + UTUN_SRC_IP = (Inet4Address) InetAddress.getByName("169.254.3.4"); // "this host" + UTUN_DST_IP = (Inet4Address) InetAddress.getByName("169.254.3.5"); // "other end" + } catch (UnknownHostException e) { + throw new IllegalStateException(e); + } + } + + /** + * Dummy method to indicate the given parameter is not checked by our test code. + * + * @param v The parameter. + * @return The parameter. + */ + private static Object unchecked(Object v) { + return v; + } + + /** + * Returns the given IPv4 address as an integer. + * + * @param addr The IPv4 address object. + * @return The integer. + */ + private static int getAddressAsInt(Inet4Address addr) { + // In the JDK implementation of Inet4Address, this happens to be the hash code. + return addr.hashCode(); + } + + @SuppressWarnings({ + "checkstyle:VariableDeclarationUsageDistance", "PMD.JUnitTestContainsTooManyAsserts", + "PMD.AvoidBranchingStatementAsLastInLoop"}) + @Test + @ExecutionEnvironmentRequirement(root = Rule.REQUIRED) + @AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_DARWIN) + public void testTunnelPingPong() throws Exception { + try (AFSYSTEMDatagramSocket socket = AFSYSTEMDatagramSocket.newInstance()) { + int id = socket.getNodeIdentity(WellKnownKernelControlNames.UTUN_CONTROL); + + // NOTE: Connecting requires root privileges, but we could do that in a separate process + // and send the socket FD via AF_UNIX to a non-privileged helper process. + try { + socket.connect(AFSYSTEMSocketAddress.ofSysAddrIdUnit(SysAddr.AF_SYS_CONTROL, id, 0)); + } catch (SocketException e) { + assumeTrue(false, "Could not connect to UTUN_CONTROL: " + e); + return; + } + + assertTimeoutPreemptively(Duration.ofSeconds(5), () -> { + + AFSYSTEMSocketAddress rsa = socket.getRemoteSocketAddress(); + Objects.requireNonNull(rsa); + + assertEquals(SysAddr.AF_SYS_CONTROL, rsa.getSysAddr()); + assertEquals(id, rsa.getId()); + assertNotEquals(0, rsa.getUnit()); // utunN: N=(unit-1), e.g., unit=9 -> utun8 + + String utun = "utun" + (rsa.getUnit() - 1); + // System.out.println(utun); + + Process p = Runtime.getRuntime().exec(new String[] { + "/sbin/ifconfig", utun, UTUN_SRC_IP.getHostAddress(), UTUN_DST_IP.getHostAddress()}); + int rcIfconfig; + try { + rcIfconfig = p.waitFor(); + } finally { + p.destroyForcibly(); + } + + assertEquals(0, rcIfconfig, "Could not set IP address for " + utun); + + AFSYSTEMDatagramChannel channel = socket.getChannel(); + ByteBuffer bb = ByteBuffer.allocateDirect(1500).order(ByteOrder.BIG_ENDIAN); + + CompletableFuture ping = CompletableFuture.supplyAsync(() -> { + try { + return UTUN_DST_IP.isReachable(1000); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + }); + + while (channel.read(bb) >= 0) { + bb.flip(); + + // Request: Domain (AF_INET) + IPv4 header + ICMP header + ICMP payload + + int totalSize = bb.remaining(); + // assertEquals(76, totalSize); // 4 byte domain header + 72 bytes packet length + + int domain = bb.getInt(); + assertEquals(IPUtil.DOMAIN_AF_INET, domain, "Expect domain 2 (AF_INET)"); + + int ipHeaderStartPos = bb.position(); + + int versionAndIHL = bb.get() & 0xFF; + int version = versionAndIHL >> 4; + assertEquals(4, version, "expect IPv4 packet"); + + // see https://en.wikipedia.org/wiki/Internet_Protocol_version_4#Header + + int ihl = versionAndIHL & 0b1111; + int ihlBytes = ihl * 32 /* bit */ / 8; + assertTrue(ihlBytes >= 20, "expect (at least) 20 bytes header length"); + + int tosDSCP = (bb.get() & 0xFF); + unchecked(tosDSCP); + + int totalLen = (bb.getShort() & 0xFFFF); + assertEquals(totalSize - 4, totalLen); + + int identification = (bb.getShort() & 0xFFFF); + unchecked(identification); + + int flagsAndFragmentOffset = (bb.getShort() & 0xFFFF); + int flags = flagsAndFragmentOffset >> 13; + int fragmentOffset = flagsAndFragmentOffset & 0b1_1111_1111_1111; + assertEquals(0, flags); + assertEquals(0, fragmentOffset); + + int ttl = bb.get() & 0xFF; + assertNotEquals(0, ttl); // e.g., 65 + + int protocol = bb.get() & 0xFF; + assertEquals(IPUtil.AF_INET_PROTOCOL_ICMP, protocol); // 1 == ICMP + + int headerChecksum = bb.getShort() & 0xFFFF; + // see below for verification + + int srcIP = bb.getInt(); + int dstIP = bb.getInt(); + + assertEquals(getAddressAsInt(UTUN_SRC_IP), srcIP); // 10.250.3.4 + assertEquals(getAddressAsInt(UTUN_DST_IP), dstIP); // 10.250.3.5 + + // when ihl=5 -> ihlBytes=ihl*4=20, there are no more options + // but let's check nevertheless... + + int remainingHeaderLength = ihlBytes - 20; + if (remainingHeaderLength > 0) { + System.err.println("Warning: Found unexpected Options section in IPv4 header; len=" + + remainingHeaderLength); + bb.position(bb.position() + remainingHeaderLength); + } + + // we're at the end of the IPv4 header + + int computedHeaderChecksum = IPUtil.checksumIPv4header(bb, ipHeaderStartPos, bb + .position()); + assertEquals(computedHeaderChecksum, headerChecksum); + + int icmpSize = bb.remaining(); + // assertEquals(52, icmpSize); // ICMP header + optional data + + int icmpBeginPosition = bb.position(); + + // begin ICMP header + int icmpType = bb.get() & 0xFF; + assertEquals(8, icmpType); // 8 = Echo Request + + int icmpCode = bb.get() & 0xFF; + assertEquals(0, icmpCode); // Echo Request has no other Code + + int icmpChecksum = bb.getShort() & 0xFFFF; // checked below + + int icmpEchoIdentifier = bb.getShort() & 0xFFFF; + int icmpEchoSequenceNumber = bb.getShort() & 0xFFFF; + + unchecked(icmpEchoIdentifier); + assertEquals(1, icmpEchoSequenceNumber); // first echo packet + + int icmpChecksumComputed = // + IPUtil.checksumICMPheader(bb, icmpBeginPosition, bb.position() + bb.remaining()); + assertEquals(icmpChecksumComputed, icmpChecksum); + + // Now it's time to craft an echo response + // Response: "AF_INET" + IPv4 header + ICMP header + ICMP payload from echo request + + ByteBuffer response = ByteBuffer.allocate(IPUtil.DOMAIN_HEADER_LENGTH + + IPUtil.IPV4_DEFAULT_HEADER_SIZE + icmpSize).order(ByteOrder.BIG_ENDIAN); + response.putInt(IPUtil.DOMAIN_AF_INET); + IPUtil.putIPv4Header(response, icmpSize, IPUtil.AF_INET_PROTOCOL_ICMP, dstIP, srcIP); + + int responsePayloadStart = response.position(); + IPUtil.checksumIPv4header(response, IPUtil.DOMAIN_HEADER_LENGTH, responsePayloadStart); + + IPUtil.putICMPEchoResponse(response, (short) icmpEchoIdentifier, + (short) icmpEchoSequenceNumber, bb); + assertEquals(0, bb.remaining()); // writeEchoResponse consumed the payload + int responsePayloadEnd = response.position(); + + IPUtil.checksumICMPheader(response, responsePayloadStart, responsePayloadEnd); + + response.flip(); + int written = channel.write(response); + bb.clear(); + + assertEquals(response.capacity(), written); + + assertTrue(ping.get(1, TimeUnit.SECONDS)); + + return; + } + + fail("Nothing received"); + }); + } + } +} diff --git a/junixsocket-demo-jpackagejlink/README.md b/junixsocket-demo-jpackagejlink/README.md new file mode 100644 index 000000000..1d3692fc2 --- /dev/null +++ b/junixsocket-demo-jpackagejlink/README.md @@ -0,0 +1,53 @@ +# junixsocket-demo-jpackagejlink + +Example code to demonstrate how to use junixsocket with jlink and jpackage. + +## Definitions + +### jlink + +jlink builds Java runtime images that are tailored towards a specific set of Java classes / modules. + +jlink was introduced in Java 9. + +### jpackage + +jpackage builds native installers for applications, utilizing optimized runtimes very much like jlink. + +jpackage was introduced in Java 14. + +## The demo code + +This demo simply runs a very simple selftest built into junixsocket-common. + +It depends on `junixsocket-core`, which is a POM-only dependency that in turn references +`junixsocket-common` (the common junixsocket API) and `junixsocket-native-common` (the +native library images for common operating systems). + +## Build instructions + +The main purpose of this Maven module is to demonstrate how to configure a project's `pom.xml` file +such that jlink/jpackage-compatible artifacts can be created. + +By default, this demo does not create executable jlink/jpackage artifacts. +You have to invoke the `mvn` command with a given profile setting to enable them: + + cd junixsocket-demo-jpackagejlink + mvn clean verify -Djpackage -Djlink + +Omit either `-Djpackage` or `-Djlink` if desired. + +See the [junixsocket-demo-jpackagejlink POM file](pom.xml) for how this is done. + +## Results + +See `junixsocket-demo-jpackagejlink/target/jpackage` for the binary package result (the actual file +depends on your target platform). + +See `junixsocket-demo-jpackagejlink/target/jlink/image` for the Java runtime optimized for the demo +code. + +## References + +* [Tools Reference: jlink](https://docs.oracle.com/en/java/javase/11/tools/jlink.html) +* [Tools Reference: jpackage](https://docs.oracle.com/en/java/javase/14/docs/specs/man/jpackage.html) diff --git a/junixsocket-demo-jpackagejlink/pom.xml b/junixsocket-demo-jpackagejlink/pom.xml new file mode 100644 index 000000000..9046a2a8a --- /dev/null +++ b/junixsocket-demo-jpackagejlink/pom.xml @@ -0,0 +1,258 @@ + + + 4.0.0 + junixsocket-demo-jpackagejlink + jar + + com.kohlschutter.junixsocket + junixsocket + 2.10.1 + ../pom.xml + + junixsocket-demo-jpackagejlink + + ${project.parent.basedir} + org.newsclub.net.unix.demo.jpackagejlink.DemoMainClass + + + junixsocket jpackage/jlink demo setup + + + + jpackage + + + jpackage + + + + + + maven-resources-plugin + + + jpackage-copy-main-jar + verify + + copy-resources + + + + ${project.build.directory}/jpackage-app + + + + ${project.build.directory} + + + ${project.name}-${project.version}.jar + + + + + + + + + com.github.akman + jpackage-maven-plugin + + + verify + + jpackage + + + + ${mainClass} + PLATFORM + + + + + true + false + + glob:**/*.jar + + + + + + java.base + + org.newsclub.net.unix + + + com.kohlschutter.junixsocket.nativecommon + + + org.newsclub.net.unix.demo.jpackagejlink + + + ${project.build.directory}/jpackage-app + + ${project.name}-${project.version}.jar + + + + + + + + + + com.github.akman + jpackage-maven-plugin + 0.1.5 + + + + + + + + jlink + + + jlink + + + + + + com.github.akman + jlink-maven-plugin + + + verify + + jlink + + + + junixsocket-jlink-demo + + org.newsclub.net.unix.demo.jpackagejlink + + ${mainClass} + + + + + + true + false + + glob:**/*.jar + + + + + + java.base + + org.newsclub.net.unix + + + com.kohlschutter.junixsocket.nativecommon + + + org.newsclub.net.unix.demo.jpackagejlink + + + + + + + + + + + com.github.akman + jlink-maven-plugin + 0.1.11 + + + + + + + + maven-jlink + + + maven-jlink + + + + + + org.apache.maven.plugins + maven-jlink-plugin + + + verify + + jlink + + + jlink + + junixsocket-jlink-demo=org.newsclub.net.unix.demo.jpackagejlink/${mainClass} + + + java.base + + org.newsclub.net.unix + + + com.kohlschutter.junixsocket.nativecommon + + + org.newsclub.net.unix.demo.jpackagejlink + + + + + + + + + + + org.apache.maven.plugins + maven-jlink-plugin + 3.2.0 + + + + + + + + + + + sonatype.snapshots + Sonatype snapshot repository + https://oss.sonatype.org/content/repositories/snapshots/ + default + + true + + + + + + + com.kohlschutter.junixsocket + junixsocket-core + pom + + + diff --git a/junixsocket-demo-jpackagejlink/src/main/java/module-info.java b/junixsocket-demo-jpackagejlink/src/main/java/module-info.java new file mode 100644 index 000000000..e8cd1060a --- /dev/null +++ b/junixsocket-demo-jpackagejlink/src/main/java/module-info.java @@ -0,0 +1,11 @@ +/** + * jpackage/jlink demo module + */ +module org.newsclub.net.unix.demo.jpackagejlink { + exports org.newsclub.net.unix.demo.jpackagejlink; + + requires java.base; + + requires org.newsclub.net.unix; + requires com.kohlschutter.junixsocket.nativecommon; +} diff --git a/junixsocket-demo-jpackagejlink/src/main/java/org/newsclub/net/unix/demo/jpackagejlink/DemoMainClass.java b/junixsocket-demo-jpackagejlink/src/main/java/org/newsclub/net/unix/demo/jpackagejlink/DemoMainClass.java new file mode 100644 index 000000000..5c713e25d --- /dev/null +++ b/junixsocket-demo-jpackagejlink/src/main/java/org/newsclub/net/unix/demo/jpackagejlink/DemoMainClass.java @@ -0,0 +1,41 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.demo.jpackagejlink; + +import org.newsclub.net.unix.AFUNIXSocket; + +/** + * The entrypoint class for the jpackage or jlink demo binary. + * + * @author Christian Kohlschütter + */ +public final class DemoMainClass { + + private DemoMainClass() { + } + + /** + * The entrypoint method for the jpackage or jlink demo binary. + * + * @param args The program arguments. + */ + public static void main(String[] args) { + // run a simple built-in selftest + AFUNIXSocket.main(new String[0]); + } +} diff --git a/junixsocket-demo/.gitignore b/junixsocket-demo/.gitignore index bf65d59b3..f4177d054 100644 --- a/junixsocket-demo/.gitignore +++ b/junixsocket-demo/.gitignore @@ -1 +1,3 @@ /target-eclipse/ +/juxclient.* +/juxserver.* diff --git a/junixsocket-demo/pom.xml b/junixsocket-demo/pom.xml index 5f2b50659..cf4d9ee6a 100644 --- a/junixsocket-demo/pom.xml +++ b/junixsocket-demo/pom.xml @@ -1,12 +1,14 @@ - + 4.0.0 junixsocket-demo jar com.kohlschutter.junixsocket junixsocket - 2.6.1 + 2.10.1 ../pom.xml junixsocket-demo @@ -14,64 +16,45 @@ ${project.parent.basedir} 8 - Some example code to demo junixsocket's features - - com.kohlschutter.junixsocket junixsocket-core - ${project.version} pom com.kohlschutter.junixsocket junixsocket-tipc - ${project.version} com.kohlschutter.junixsocket junixsocket-mysql - ${project.version} com.kohlschutter.junixsocket junixsocket-server - ${project.version} com.kohlschutter.junixsocket junixsocket-rmi - ${project.version} - mysql - mysql-connector-java - 8.0.30 - - - com.google.protobuf - protobuf-java - - - + com.kohlschutter.junixsocket + junixsocket-ssl - - com.google.protobuf - protobuf-java - 3.19.6 + com.mysql + mysql-connector-j - org.postgresql postgresql - 42.5.0 + 42.7.3 provided @@ -90,10 +73,29 @@ com.squareup.okhttp3 okhttp - 4.10.0 + 4.12.0 + + + org.apache.mina + mina-core + 2.2.3 + + + org.slf4j + slf4j-api + + + + + io.netty + netty-all + 4.1.109.Final + + + org.slf4j + slf4j-simple - diff --git a/junixsocket-demo/src/main/java/module-info.java b/junixsocket-demo/src/main/java/module-info.java index 0082407bf..172ef6c32 100644 --- a/junixsocket-demo/src/main/java/module-info.java +++ b/junixsocket-demo/src/main/java/module-info.java @@ -6,13 +6,19 @@ requires org.newsclub.net.unix; requires org.newsclub.net.unix.tipc; requires org.newsclub.net.unix.server; + requires org.newsclub.net.unix.ssl; requires org.newsclub.net.mysql; requires transitive org.newsclub.net.unix.rmi; requires java.rmi; requires java.sql; requires nanohttpd; requires okhttp3; + requires mina.core; + requires io.netty.common; + requires io.netty.buffer; + requires io.netty.transport; requires com.kohlschutter.util; + requires static com.kohlschutter.annotations.compiletime; requires static org.eclipse.jdt.annotation; diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/mysql/demo/AFUNIXDatabaseSocketFactoryDemo.java b/junixsocket-demo/src/main/java/org/newsclub/net/mysql/demo/AFUNIXDatabaseSocketFactoryDemo.java index a91d97440..ff847e1e9 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/mysql/demo/AFUNIXDatabaseSocketFactoryDemo.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/mysql/demo/AFUNIXDatabaseSocketFactoryDemo.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ /** * Demonstrates how to connect to a local MySQL server. - * + * * @author Christian Kohlschuetter */ public class AFUNIXDatabaseSocketFactoryDemo { diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/DemoHelper.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/DemoHelper.java index 50ca192f9..7b9371bb2 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/DemoHelper.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/DemoHelper.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,7 +37,7 @@ * Just a helper class to simplify controlling the demo from the command line. */ public final class DemoHelper { - @ExcludeFromCodeCoverageGeneratedReport + @ExcludeFromCodeCoverageGeneratedReport(reason = "unreachable") private DemoHelper() { throw new IllegalStateException("No instances"); } @@ -45,7 +45,7 @@ private DemoHelper() { /** * Adds a key-value pair to a Properties instance. Takes values from a given system property and * overrides the default value with it. - * + * * @param props The Properties instance to write to. * @param key The name of the property. * @param defaultValue The default value (for demo purposes) diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/SimpleTestClient.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/SimpleTestClient.java index 694577593..bc1a2acdc 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/SimpleTestClient.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/SimpleTestClient.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,18 +24,19 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.SocketException; +import java.nio.charset.StandardCharsets; import org.newsclub.net.unix.AFUNIXSocket; import org.newsclub.net.unix.AFUNIXSocketAddress; /** * A simple demo client. - * + * * Reads a server greeting string, then sends a "Hello Server" string as a response. - * + * * Finally, reads integers (via {@link DataInputStream}), then sends twice the sent value each, * unless a "-123" magic number is read to indicate the end of the conversation. - * + * * @author Christian Kohlschütter * @see SimpleTestServer */ @@ -56,10 +57,10 @@ public static void main(String[] args) throws IOException { byte[] buf = new byte[128]; int read = is.read(buf); - System.out.println("Server says: " + new String(buf, 0, read, "UTF-8")); + System.out.println("Server says: " + new String(buf, 0, read, StandardCharsets.UTF_8)); System.out.println("Replying to server..."); - os.write("Hello Server".getBytes("UTF-8")); + os.write("Hello Server".getBytes(StandardCharsets.UTF_8)); os.flush(); System.out.println("Now reading numbers from the server..."); diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/SimpleTestServer.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/SimpleTestServer.java index 9d80664ce..23bce5e7f 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/SimpleTestServer.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/SimpleTestServer.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import org.newsclub.net.unix.AFUNIXServerSocket; import org.newsclub.net.unix.AFUNIXSocket; @@ -31,16 +32,17 @@ /** * A simple demo server. - * + * * Sends a hello message (as a string), then reads back a response string. - * + * * Finally, sends integers (via {@link DataOutputStream}) from 1 to 5, expects an integer response * of twice the sent value each, then sends a "-123" magic number to indicate the end of the * conversation. - * + * * @author Christian Kohlschütter * @see SimpleTestClient */ +@SuppressWarnings({"CatchAndPrintStackTrace" /* errorprone */, "PMD.CognitiveComplexity"}) public final class SimpleTestServer { private static final int MAX_NUMBER = 5; @@ -56,18 +58,18 @@ public static void main(String[] args) throws IOException { try (AFUNIXServerSocket server = AFUNIXServerSocket.newInstance()) { /* * Uncomment the code below to change the bind behavior: - * + * * By default ("reuseAddress" is true), attempting to bind while another server is running on * the same address will cause the first server to terminate, and the new server will take * over the address. Depending on the operating system, this may involve connecting to the * first server in order to "wake up" the accept call. - * + * * In this demo code, we use AFSocket.getConnectionStatus to see if the accepted connection is * alive by sending - * + * * When "reuseAddress" is false, attempting to bind while another server is running won't * disrupt the first connection. The second bind will throw a SocketException instead. - * + * * NOTE: "reuseAddress=true" may not yet be supported on certain operating systems, such as * IBM i and z/OS. On these platforms, the behavior is as if "reuseAddress=false". Please * reach out by filing an issue on https://github.com/kohlschutter/junixsocket/issues if this @@ -102,7 +104,7 @@ public static void main(String[] args) throws IOException { } System.out.println("Saying hello to client " + sock); - os.write("Hello, dear Client".getBytes("UTF-8")); + os.write("Hello, dear Client".getBytes(StandardCharsets.UTF_8)); os.flush(); byte[] buf = new byte[128]; diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/DemoClient.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/DemoClient.java index 7faf9b515..2cf3a61d7 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/DemoClient.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/DemoClient.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ /** * A demo program to configure and run several {@link AFSocket} client demos from the command line. - * + * * @author Christian Kohlschütter */ public final class DemoClient { diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/DemoClientBase.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/DemoClientBase.java index 886406806..007bcef10 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/DemoClientBase.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/DemoClientBase.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ /** * An {@link AFUNIXSocket} client that's just good for demo purposes. - * + * * @author Christian Kohlschütter */ abstract class DemoClientBase { diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/ReadClient.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/ReadClient.java index b5886ba52..467bcc044 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/ReadClient.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/ReadClient.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/ReadFileHandleClient.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/ReadFileHandleClient.java index a4a113adb..1b371ad8f 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/ReadFileHandleClient.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/ReadFileHandleClient.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/ReadWriteClient.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/ReadWriteClient.java index cb71c581f..f805eeb47 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/ReadWriteClient.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/client/ReadWriteClient.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ /** * A simple bidirectional Unix socket client that reads from/writes to stdin/stdout. */ +@SuppressWarnings("CatchAndPrintStackTrace" /* errorprone */) public final class ReadWriteClient extends DemoClientBase { @Override protected void handleSocket(Socket socket) throws IOException { diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/jdbc/PostgresDemo.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/jdbc/PostgresDemo.java index da854ab74..24690981c 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/jdbc/PostgresDemo.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/jdbc/PostgresDemo.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,7 @@ /** * Demonstrates how to connect to a local PostgreSQL server via unix sockets. - * + * * @author Christian Kohlschuetter * @see AFUNIXSocketFactory */ diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/mina/MinaTimeServer.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/mina/MinaTimeServer.java new file mode 100644 index 000000000..c8e27440e --- /dev/null +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/mina/MinaTimeServer.java @@ -0,0 +1,62 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.demo.mina; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.apache.mina.core.service.IoAcceptor; +import org.apache.mina.core.session.IdleStatus; +import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.mina.filter.codec.textline.TextLineCodecFactory; +import org.apache.mina.filter.logging.LoggingFilter; +import org.apache.mina.transport.socket.nio.NioSocketAcceptor; +import org.newsclub.net.unix.AFUNIXSelectorProvider; +import org.newsclub.net.unix.AFUNIXSocketAddress; + +/** + * Apache Mina-based Time server, modified to use junixsocket. + * + * Based on example code from + * + * Apache Mina user guide, chapter 2.2 — Sample TCP Server + */ +public class MinaTimeServer { + public static void main(String[] args) throws IOException { + int processorCount = Runtime.getRuntime().availableProcessors() + 1; + + // IoAcceptor acceptor = new NioSocketAcceptor(processorCount); // from original example code + IoAcceptor acceptor = new NioSocketAcceptor(processorCount, AFUNIXSelectorProvider.provider()); + // IoAcceptor acceptor = new NioSocketAcceptor(processorCount, + // AFTIPCSelectorProvider.provider()); + + acceptor.getFilterChain().addLast("logger", new LoggingFilter()); + acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory( + StandardCharsets.UTF_8))); + acceptor.setHandler(new TimeServerHandler()); + acceptor.getSessionConfig().setReadBufferSize(2048); + acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10); + + // acceptor.bind( new InetSocketAddress(PORT) ); // from original example code + AFUNIXSocketAddress addr = AFUNIXSocketAddress.of(new File("/tmp/minatime")); + acceptor.bind(addr); + // acceptor.bind(AFTIPCSocketAddress.ofService(Scope.SCOPE_CLUSTER, 128, 1)); + System.out.println("Bound to " + addr); + } +} \ No newline at end of file diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/mina/TimeServerHandler.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/mina/TimeServerHandler.java new file mode 100644 index 000000000..75225e1a6 --- /dev/null +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/mina/TimeServerHandler.java @@ -0,0 +1,56 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.demo.mina; + +import java.util.Date; + +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IdleStatus; +import org.apache.mina.core.session.IoSession; + +/** + * Time server handler. + * + * Based on example code from + * + * Apache Mina user guide, chapter 2.2 — Sample TCP Server + */ +@SuppressWarnings("JavaUtilDate" /* errorprone */) +class TimeServerHandler extends IoHandlerAdapter { + @Override + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { + cause.printStackTrace(); + } + + @Override + public void messageReceived(IoSession session, Object message) throws Exception { + String str = message.toString(); + if ("quit".equalsIgnoreCase(str.trim())) { + session.closeNow(); + return; + } + Date date = new Date(); + session.write(date.toString()); + System.out.println("Message written..."); + } + + @Override + public void sessionIdle(IoSession session, IdleStatus status) throws Exception { + System.out.println("IDLE " + session.getIdleCount(status)); + } +} diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/nanohttpd/NanoHttpdServerDemo.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/nanohttpd/NanoHttpdServerDemo.java index 133953cdb..d7dac583f 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/nanohttpd/NanoHttpdServerDemo.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/nanohttpd/NanoHttpdServerDemo.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,13 +32,13 @@ /** * Creates a {@link NanoHTTPD} server, bound to {@code /tmp/junixsocket-http-server.sock}. - * + * * Http requests on that socket should return "Hello world from <hostname>". - * + * * @author Christian Kohlschütter * @see OkHttpClientDemo */ -public class NanoHttpdServerDemo extends NanoHTTPD { +public final class NanoHttpdServerDemo extends NanoHTTPD { public NanoHttpdServerDemo(SocketAddress socketAddress) throws IOException { super(0); diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/netty/EchoServer.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/netty/EchoServer.java new file mode 100644 index 000000000..c4c7e2856 --- /dev/null +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/netty/EchoServer.java @@ -0,0 +1,96 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.demo.netty; + +import java.io.File; +import java.nio.channels.spi.SelectorProvider; +import java.util.concurrent.Executor; + +import org.newsclub.net.unix.AFSocketAddress; +import org.newsclub.net.unix.AFUNIXSelectorProvider; +import org.newsclub.net.unix.AFUNIXSocketAddress; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; + +/** + * Echos any incoming data. + *

+ * Based on example code from Netty user + * guide for 4.x + */ +@SuppressWarnings("FutureReturnValueIgnored" /* errorprone */) +public class EchoServer { + private final AFSocketAddress addr; + + public EchoServer(AFSocketAddress addr) { + this.addr = addr; + } + + public void run() throws Exception { + SelectorProvider provider = AFUNIXSelectorProvider.provider(); + // SelectorProvider provider = AFTIPCSelectorProvider.provider(); + + // We need to specify our custom selector provider here (1), as well as in (3) + EventLoopGroup bossGroup = new NioEventLoopGroup(0, (Executor) null, provider); // (1) + EventLoopGroup workerGroup = new NioEventLoopGroup(0, (Executor) null, provider); // (1) + try { + ServerBootstrap b = new ServerBootstrap(); // (2) + b.group(bossGroup, workerGroup) // + .channelFactory(() -> new NioServerSocketChannel(provider)) // (3) + .childHandler(new ChannelInitializer() { // (4) + @Override + public void initChannel(SocketChannel ch) throws Exception { + ch.pipeline().addLast(new EchoServerHandler()); + } + }) // + .option(ChannelOption.SO_BACKLOG, 128) // (5) + .childOption(ChannelOption.SO_KEEPALIVE, true); // (6) + + // Bind and start to accept incoming connections. + ChannelFuture f = b.bind(addr).sync(); // (7) + + // Wait until the server socket is closed. + // In this example, this does not happen, but you can do that to gracefully + // shut down your server. + f.channel().closeFuture().sync(); + } finally { + workerGroup.shutdownGracefully(); + bossGroup.shutdownGracefully(); + } + } + + public static void main(String[] args) throws Exception { + File path = new File("/tmp/nettyecho"); + if (args.length > 0) { + path = new File(args[0]); + } + + AFUNIXSocketAddress addr = AFUNIXSocketAddress.of(path); + System.out.println("Binding to " + addr); + + new EchoServer(addr).run(); + // new EchoServer(AFTIPCSocketAddress.ofService(Scope.SCOPE_CLUSTER, 128, 3)).run(); + } +} \ No newline at end of file diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/netty/EchoServerHandler.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/netty/EchoServerHandler.java new file mode 100644 index 000000000..52a699203 --- /dev/null +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/netty/EchoServerHandler.java @@ -0,0 +1,44 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.demo.netty; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +/** + * Handles a server-side channel. + *

+ * Based on example code from Netty user + * guide for 4.x + */ +@SuppressWarnings("FutureReturnValueIgnored" /* errorprone */ ) +class EchoServerHandler extends ChannelInboundHandlerAdapter { // (1) + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + ctx.write(msg); // (1) + ctx.flush(); // (2) + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4) + // Close the connection when an exception is raised. + cause.printStackTrace(); + ctx.close(); + } +} \ No newline at end of file diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/okhttp/OkHttpClientDemo.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/okhttp/OkHttpClientDemo.java index 4d4086b1d..77df83f24 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/okhttp/OkHttpClientDemo.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/okhttp/OkHttpClientDemo.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,9 +38,9 @@ /** * Connects to {@code /tmp/junixsocket-http-server.sock} and performs an http request over that * socket, using the OkHttp HTTP client library. - * + * * If that socket is bound by {@link NanoHttpdServerDemo}, the expected output is "Hello world". - * + * * @author Christian Kohlschütter * @see NanoHttpdServerDemo */ diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/okhttp/OkHttpClientTIPCDemo.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/okhttp/OkHttpClientTIPCDemo.java index 1e1f9d8d6..18069c225 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/okhttp/OkHttpClientTIPCDemo.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/okhttp/OkHttpClientTIPCDemo.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,13 +39,14 @@ /** * Connects to TIPC service 8080.1 and performs an HTTP request over that socket, using the * OkHttp HTTP client library. - * + * * If that socket is bound by {@link NanoHttpdServerDemo}, the expected output is "Hello world from * <hostname>" (start {@link NanoHttpdServerDemo} with {@code --url tipc://8080.1}. - * + * * @author Christian Kohlschütter * @see NanoHttpdServerDemo */ +@SuppressWarnings("CatchAndPrintStackTrace" /* errorprone */) public class OkHttpClientTIPCDemo { public static void main(String[] args) throws IOException, InterruptedException { diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/HelloWorldImpl.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/HelloWorldImpl.java index 6e3d12d3c..0fb4d060b 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/HelloWorldImpl.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/HelloWorldImpl.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ /** * The implementation of the very simple {@link HelloWorld} service. - * + * * @author Christian Kohlschütter */ public class HelloWorldImpl implements HelloWorld { @@ -37,7 +37,7 @@ public class HelloWorldImpl implements HelloWorld { /** * Creates a new {@link HelloWorld} implementation. - * + * * @param naming The naming instance to use. */ @SuppressFBWarnings("EI_EXPOSE_REP") diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/SimpleRMIClient.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/SimpleRMIClient.java index aef5a3a6a..509fd43f7 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/SimpleRMIClient.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/SimpleRMIClient.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ /** * A simple RMI client. Locates the RMI registry via AF_UNIX sockets and calls * {@link HelloWorld#hello()}. - * + * * @author Christian Kohlschütter */ public final class SimpleRMIClient { diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/SimpleRMIClientActingAsServer.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/SimpleRMIClientActingAsServer.java index 082856410..c45c9af4e 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/SimpleRMIClientActingAsServer.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/SimpleRMIClientActingAsServer.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,9 +30,10 @@ /** * A simple RMI client. Locates the RMI registry via AF_UNIX sockets and calls * {@link HelloWorld#hello()}. - * + * * @author Christian Kohlschütter */ +@SuppressWarnings("CatchAndPrintStackTrace" /* errorprone */) public final class SimpleRMIClientActingAsServer { public static void main(String[] args) throws IOException, NotBoundException { AFUNIXNaming naming = AFUNIXNaming.getInstance(); @@ -57,16 +58,16 @@ public static void main(String[] args) throws IOException, NotBoundException { System.out.println("Calling HelloWorld..."); System.out.println(obj.hello() + " " + obj.world() + "!"); - /** + /* * Uncommenting the line below keeps this instance running. - * + * * Try it and run SimpleRMIClient to see the difference. */ naming.unexportAndUnbind("world", world); - /** + /* * Also try to remotely shut down the registry. - * + * * This will not succeed if the server set {@code naming.setRemoteShutdownAllowed(false)}. See * {@link SimpleRMIServer} */ diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/SimpleRMIServer.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/SimpleRMIServer.java index c7e73c41f..0c6803d7c 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/SimpleRMIServer.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/SimpleRMIServer.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ /** * A very simple RMI server. Provides a registry and the implementation of the {@link HelloWorld} * service. - * + * * @author Christian Kohlschütter */ public final class SimpleRMIServer { diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/WorldImpl.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/WorldImpl.java index 1c100b131..17fb4440d 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/WorldImpl.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/WorldImpl.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ /** * The implementation of the very simple {@link World} service. - * + * * @author Christian Kohlschütter */ public class WorldImpl implements World { @@ -31,7 +31,7 @@ public class WorldImpl implements World { /** * Creates a new {@link World} instance. - * + * * @param text The text to return upon calling {@link #world()}. */ public WorldImpl(String text) { diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/fd/StreamClient.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/fd/StreamClient.java index 991def136..43da47371 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/fd/StreamClient.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/fd/StreamClient.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ /** * Demonstrates how to read files via FileDescriptors that are exchanged via RMI. - * + * * @author Christian Kohlschütter * @see StreamServer */ @@ -42,7 +42,7 @@ private StreamClient() { /** * {@link StreamClient} command-line tool. - * + * * @param args Command-line arguments. * @throws IOException on error. * @throws NotBoundException if the server cannot be reached. diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/fd/StreamServer.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/fd/StreamServer.java index b432a7ca9..2f4cdacf4 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/fd/StreamServer.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/fd/StreamServer.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,14 +24,14 @@ /** * Demonstrates how to read/write files via FileDescriptors that are exchanged via RMI. - * + * * This allows reading/writing from and to files that are otherwise not even accessible by the user. * For example, starting the {@link StreamServer} as root and the {@link StreamClient} as a * non-privileged user will allow the non-privileged user to read files only accessible to root. - * + * * NOTE: For obvious security reasons, running this server without modification is not advised for * anything other than demo purposes. - * + * * @author Christian Kohlschütter * @see StreamClient */ @@ -42,7 +42,7 @@ private StreamServer() { /** * {@link StreamServer} command-line tool. - * + * * @param args Command-line arguments. * @throws IOException on error. * @throws AlreadyBoundException if there was already a server running. diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/fd/StreamServiceImpl.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/fd/StreamServiceImpl.java index 6ac3eeb55..47af46a73 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/fd/StreamServiceImpl.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/fd/StreamServiceImpl.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.AccessDeniedException; -import java.rmi.RemoteException; import org.newsclub.net.unix.demo.rmi.services.StreamService; import org.newsclub.net.unix.rmi.AFUNIXRMISocketFactory; @@ -35,7 +34,7 @@ /** * An implementation of {@link StreamService}. - * + * * @author Christian Kohlschütter */ public class StreamServiceImpl implements StreamService, Closeable { @@ -43,12 +42,11 @@ public class StreamServiceImpl implements StreamService, Closeable { /** * Creates a new instance. - * + * * @param socketFactory The socket factory to use. - * @throws RemoteException on error. */ @SuppressFBWarnings("EI_EXPOSE_REP") - public StreamServiceImpl(AFUNIXRMISocketFactory socketFactory) throws RemoteException { + public StreamServiceImpl(AFUNIXRMISocketFactory socketFactory) { this.socketFactory = socketFactory; } @@ -82,7 +80,7 @@ public RemoteCloseableImpl openForWriting(File path) throws IO /** * Checks if the given path may be accessed for reading. - * + * * @param path The path to check. * @return {@code true} if permitted. */ @@ -92,7 +90,7 @@ protected boolean mayRead(File path) { /** * Checks if the given path may be accessed for writing. - * + * * @param path The path to check. * @return {@code true} if permitted. */ diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/services/HelloWorld.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/services/HelloWorld.java index bd004d294..338786019 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/services/HelloWorld.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/services/HelloWorld.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,13 +22,13 @@ /** * A very simple "hello world" service. - * + * * @author Christian Kohlschütter */ public interface HelloWorld extends Remote { /** * Returns "Hello". - * + * * @return "Hello" * @throws IOException if the operation fails. */ @@ -36,7 +36,7 @@ public interface HelloWorld extends Remote { /** * Returns "World" (or something else?). - * + * * @return "World" (usually) * @throws IOException if the operation fails. */ diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/services/StreamService.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/services/StreamService.java index 5c56cf14b..08d53fd8d 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/services/StreamService.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/services/StreamService.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,14 +28,14 @@ /** * The {@link StreamServer}'s RMI service. - * + * * @author Christian Kohlschütter * @see StreamServer */ public interface StreamService extends Remote { /** * Opens the given file for reading. - * + * * @param path The file to open. * @return A remote instance for the file. * @throws IOException on error. @@ -44,7 +44,7 @@ public interface StreamService extends Remote { /** * Opens the given file for writing. - * + * * @param path The file to open. * @return A remote instance for the file. * @throws IOException on error. diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/services/World.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/services/World.java index d8f341a0c..f3178e1fb 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/services/World.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/rmi/services/World.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,14 +22,14 @@ /** * A very simple "world" service. - * + * * @author Christian Kohlschütter * @see HelloWorld */ public interface World extends Remote { /** * Returns "World" (or something else?). - * + * * @return "World" (usually) * @throws RemoteException if the operation fails. */ diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/ChargenServer.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/ChargenServer.java index 40338db84..5b50da505 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/ChargenServer.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/ChargenServer.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ /** * A multi-threaded unix socket server that implements a TCP-style character generator compliant * with RFC864. - * + * * @author Christian Kohlschütter */ public final class ChargenServer extends DemoServerBase { @@ -38,7 +38,7 @@ public final class ChargenServer extends DemoServerBase { /** * Defines a TCP-style character generator compliant with RFC864. - * + * * @see RFC864 */ private interface Chargen { @@ -83,9 +83,9 @@ private synchronized Chargen getChargen(Socket socket) throws SocketException { /** * A simple chargen implementation. - * + * * Even though this looks straightforward, it's not the fastest implementation. - * + * * @see FastChargen */ private static final class SimpleChargen implements Chargen { @@ -113,7 +113,7 @@ public void write(Socket socket) throws IOException { /** * A fast chargen implementation, using a pre-built data buffer that is just large enough to * always send a full array of bytes matching the socket's send buffer capacity. - * + * * @see SimpleChargen */ private static final class FastChargen implements Chargen { diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/DemoServerBase.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/DemoServerBase.java index c52f3d14c..b05431373 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/DemoServerBase.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/DemoServerBase.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,9 +30,10 @@ /** * An {@link SocketServer} that's just good for demo purposes. - * + * * @author Christian Kohlschütter */ +@SuppressWarnings("CatchAndPrintStackTrace" /* errorprone */) abstract class DemoServerBase extends SocketServer { public DemoServerBase(SocketAddress listenAddress) { super(listenAddress); @@ -117,16 +118,16 @@ protected void onSocketExceptionAfterAccept(Socket socket, SocketException e) { } @Override - protected void onServingException(Socket socket, Exception e) { + protected void onServingException(Socket socket, Throwable t) { if (socket.isClosed()) { // "Broken pipe", etc. - System.out.println("The other end disconnected (" + e.getMessage() + "): " + socket); + System.out.println("The other end disconnected (" + t.getMessage() + "): " + socket); return; } System.err.println("Exception thrown in " + socket + ", connected: " + socket.isConnected() + ", " + socket.isBound() + "," + socket.isClosed() + "," + socket.isInputShutdown() + "," + socket.isOutputShutdown()); - e.printStackTrace(); + t.printStackTrace(); } @Override diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/EchoServer.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/EchoServer.java index aac916a28..cf4ede44e 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/EchoServer.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/EchoServer.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ /** * A multi-threaded unix socket server that simply echoes all input, byte per byte. - * + * * @author Christian Kohlschütter */ public final class EchoServer extends DemoServerBase { diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/NullServer.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/NullServer.java index ac600b8a8..a34ccdc7c 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/NullServer.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/NullServer.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ /** * A multi-threaded unix socket server that simply reads all input, byte per byte, not doing * anything else with it. - * + * * @author Christian Kohlschütter */ public final class NullServer extends DemoServerBase { diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/SendFileHandleServer.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/SendFileHandleServer.java index 281b52578..8714264f7 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/SendFileHandleServer.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/SendFileHandleServer.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,15 +24,17 @@ import java.io.OutputStream; import java.net.Socket; import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; import org.newsclub.net.unix.AFUNIXSocket; /** * A multi-threaded unix socket server that simply reads all input, byte per byte, not doing * anything else with it. - * + * * @author Christian Kohlschütter */ +@SuppressWarnings("CatchAndPrintStackTrace" /* errorprone */) public final class SendFileHandleServer extends DemoServerBase { private final File file; @@ -49,12 +51,12 @@ protected void doServeSocket(Socket socket) throws IOException { doServeSocket((AFUNIXSocket) socket); } - protected void doServeSocket(AFUNIXSocket socket) throws IOException { + private void doServeSocket(AFUNIXSocket socket) throws IOException { try (InputStream is = socket.getInputStream(); OutputStream os = socket.getOutputStream(); FileInputStream fin = new FileInputStream(file)) { socket.setOutboundFileDescriptors(fin.getFD()); - os.write("FD sent via ancillary message.".getBytes("UTF-8")); + os.write("FD sent via ancillary message.".getBytes(StandardCharsets.UTF_8)); } } } diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/SocketServerDemo.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/SocketServerDemo.java index a23c742e6..8ae79ab50 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/SocketServerDemo.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/SocketServerDemo.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ /** * A demo program to configure and run several {@link SocketServer} demos from the command line. - * + * * @author Christian Kohlschütter */ public final class SocketServerDemo { diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/ZeroServer.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/ZeroServer.java index 752f4c55f..d2e3c69af 100644 --- a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/ZeroServer.java +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/server/ZeroServer.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ /** * A multi-threaded unix socket server that simply writes null-bytes, and does not attempt to read * anything. - * + * * @author Christian Kohlschütter */ // CPD-OFF diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/ssl/SSLDemoClient.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/ssl/SSLDemoClient.java new file mode 100644 index 000000000..26d9a0b58 --- /dev/null +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/ssl/SSLDemoClient.java @@ -0,0 +1,92 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.demo.ssl; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; + +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.security.auth.DestroyFailedException; + +import org.newsclub.net.unix.AFUNIXSocket; +import org.newsclub.net.unix.AFUNIXSocketAddress; +import org.newsclub.net.unix.ssl.SSLContextBuilder; + +/** + * A simple SSL demo client. + * + * @author Christian Kohlschütter + * @see SSLDemoServer + * @see SSLDemoPrerequisites + */ +public class SSLDemoClient { + public static void main(String[] args) throws InterruptedException, IOException, + GeneralSecurityException, DestroyFailedException { + + // Enable to see SSL debugging + // System.setProperty("javax.net.debug", "all"); + + AFUNIXSocketAddress addr = AFUNIXSocketAddress.of(new File("/tmp/ssldemo")); + + SSLSocketFactory clientSocketFactory = SSLContextBuilder.forClient() // + .withKeyStore(new File("juxclient.p12"), () -> "clientpass".toCharArray()) // + .withTrustStore(new File("juxclient.truststore"), () -> "clienttrustpass".toCharArray()) + .withDefaultSSLParameters((p) -> { + // A simple way to disable some undesired protocols: + // SSLParametersUtil.disableProtocols(p, "TLSv1.0", "TLSv1.1", "TLSv1.2"); + + // Uncomment to perform endpoint checking of server certificates + // p.setEndpointIdentificationAlgorithm("HTTPS"); + + }) // + .buildAndDestroyBuilder().getSocketFactory(); + + System.out.println("Connecting to " + addr); + + try (AFUNIXSocket plainSocket = AFUNIXSocket.connectTo(addr); + SSLSocket sslSocket = (SSLSocket) clientSocketFactory.createSocket(plainSocket, + // This hostname is sent to the server, unless we define an SNI hostname below + "localhost.junixsocket", // + plainSocket.getPort(), false)) { + + // Setting the client mode manually is not necessary when using SSLContextBuilder + // sslSocket.setUseClientMode(false); + + // Uncomment to send SNI hostname to server + // SSLParametersUtil.setSNIServerName(sslSocket, new SNIHostName("subdomain.example.com")); + + try (InputStream in = sslSocket.getInputStream(); + OutputStream out = sslSocket.getOutputStream()) { + plainSocket.setOutboundFileDescriptors(FileDescriptor.err); + System.out.println("Writing byte..."); + out.write(0xAF); + out.flush(); + + byte[] by = new byte[11]; + int r = in.read(by); + System.out.println("Received string: " + new String(by, 0, r, StandardCharsets.UTF_8)); + } + } + } +} diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/ssl/SSLDemoPrerequisites.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/ssl/SSLDemoPrerequisites.java new file mode 100644 index 000000000..fc0c7a55e --- /dev/null +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/ssl/SSLDemoPrerequisites.java @@ -0,0 +1,195 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.demo.ssl; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.CompletableFuture; + +/** + *

SSL-over-UNIX sockets demo.

+ *

+ * Prerequisites: + *

+ *

    + *
  1. Create server public/private key pair, valid for ~10 years, Store it as a PKCS12 file named + * "{@code juxserver.p12}": + *

    + * {@code keytool -genkeypair -alias juxserver -keyalg RSA -keysize 2048 + * -storetype PKCS12 -validity 3650 -ext san=dns:localhost.junixsocket + * -dname "CN=First and Last, OU=Organizational Unit, O=Organization, L=City, ST=State, C=XX" + * -keystore juxserver.p12 -storepass serverpass} + *

    + * Omit {@code -dname "CN=..."} to interactively specify the distinguished name for the certificate. + *

    + * You may verify the contents of this p12 file via + * {@code keytool -list -v -keystore juxserver.p12 -storepass serverpass}; omit the + * {@code -storepass...} parameters to specify the password interactively for additional security. + *

  2. + *
  3. Export the server's public key as a X.509 certificate: + *

    + * {@code keytool -exportcert -alias juxserver -keystore juxserver.p12 -storepass serverpass -file juxserver.pem} + *

    + * You may verify the contents of the certificate file via + * {@code keytool -printcert -file juxserver.pem}

  4. + *
  5. Import the server's X.509 certificate into the client truststore: + *

    + * {@code keytool -importcert -alias juxserver -keystore juxclient.truststore -storepass clienttrustpass + * -file juxserver.pem -noprompt} + *

    + * Omit {@code -noprompt} to interactively verify the imported certificate. + *

    + *

    + * You may verify the contents of this truststore via + * {@code keytool -list -v -keystore juxclient.truststore -storepass clienttrustpass}; omit the + * {@code -storepass...} parameters to specify the password interactively for additional security. + *

  6. + *
+ *

+ * If you want client authentication as well, perform these additional steps: + *

    + *
  1. Create client public/private key pair, valid for ~10 years, Store it as a PKCS12 file named + * "{@code juxclient.p12}": + *

    + * {@code keytool -genkeypair -alias juxclient -keyalg RSA -keysize 2048 -storetype PKCS12 + * -validity 3650 -ext san=dns:localhost.junixsocket + * -dname "CN=First and Last, OU=Organizational Unit, O=Organization, L=City, ST=State, C=XX" + * -keystore juxclient.p12 -storepass clientpass} + *

    + * Omit {@code -dname "CN=..."} to interactively specify the distinguished name for the certificate. + *

    + * You may verify the contents of this p12 file via + * {@code keytool -list -v -keystore juxclient.p12 -storepass clientpass}; omit the + * {@code -storepass...} parameters to specify the password interactively for additional security. + *

  2. + *
  3. Export the client's public key as a X.509 certificate: + *

    + * {@code keytool -exportcert -alias juxclient -keystore juxclient.p12 -storepass clientpass -file juxclient.pem} + *

    + * You may verify the contents of the certificate file via + * {@code keytool -printcert -file juxclient.pem}

  4. + *
  5. Import the client's X.509 certificate into the servers truststore: + *

    + * {@code keytool -importcert -alias juxclient -keystore juxserver.truststore -storepass servertrustpass + * -file juxclient.pem -noprompt} + *

    + * Omit {@code -noprompt} to interactively verify the imported certificate. + *

    + *

    + * You may verify the contents of this truststore via + * {@code keytool -list -v -keystore juxserver.truststore -storepass servertrustpass}; omit the + * {@code -storepass...} parameters to specify the password interactively for additional security. + *

  6. + *
+ * + * @author Christian Kohlschütter + * @see org.newsclub.net.unix.demo.ssl.SSLDemoServer + * @see org.newsclub.net.unix.demo.ssl.SSLDemoClient + */ +@SuppressWarnings({ + "FutureReturnValueIgnored", // errorprone + "CatchAndPrintStackTrace", // errorprone +}) +public class SSLDemoPrerequisites { + private static final boolean runCommand(String explanation, String... command) throws IOException, + InterruptedException { + System.out.println(explanation + "..."); + + StringBuilder sb = new StringBuilder(); + for (String c : command) { + sb.append(" "); + if (c.isEmpty() || c.contains(" ") || c.contains("\"")) { + sb.append("\"" + c.replace("\"", "\\\"") + "\""); + } else { + sb.append(c); + } + } + System.out.println("#" + sb); + + Process process = Runtime.getRuntime().exec(command); + + CompletableFuture.runAsync(() -> { + try (InputStream stdout = process.getInputStream()) { + byte[] buf = new byte[1024]; + int r; + while ((r = stdout.read(buf)) >= 0) { + System.out.write(buf, 0, r); + } + } catch (IOException e) { + e.printStackTrace(); + } + }); + CompletableFuture.runAsync(() -> { + try (InputStream stderr = process.getErrorStream()) { + byte[] buf = new byte[1024]; + int r; + while ((r = stderr.read(buf)) >= 0) { + System.err.write(buf, 0, r); + } + } catch (IOException e) { + e.printStackTrace(); + } + }); + + int rc = process.waitFor(); + System.out.println("rc=" + rc); + System.out.println(); + return rc == 0; + } + + public static void main(String[] args) throws Exception { + System.out.println("Working directory: " + new File("").getAbsolutePath()); + System.out.println(); + + boolean success = true; + + success &= runCommand("Generating server key pair", // + "keytool", "-genkeypair", "-alias", "juxserver", "-keyalg", "RSA", "-keysize", "2048", + "-storetype", "PKCS12", "-validity", "3650", "-ext", "san=dns:localhost.junixsocket", + "-dname", + "CN=First and Last, OU=Organizational Unit, O=Organization, L=City, ST=State, C=XX", + "-keystore", "juxserver.p12", "-storepass", "serverpass"); + + success &= runCommand("Exporting server certificate", // + ("keytool -exportcert -alias juxserver -keystore juxserver.p12 -storepass serverpass -file juxserver.pem") + .split("[ ]+")); + + success &= runCommand("Importing server certificate into client truststore", // + ("keytool -importcert -alias juxserver -keystore juxclient.truststore -storepass clienttrustpass" + + " -file juxserver.pem -noprompt").split("[ ]+")); + + success &= runCommand("Generating client key pair (optional, only for client authentication)", // + "keytool", "-genkeypair", "-alias", "juxclient", "-keyalg", "RSA", "-keysize", "2048", + "-storetype", "PKCS12", "-validity", "3650", "-ext", "san=dns:localhost.junixsocket", + "-dname", + "CN=First and Last, OU=Organizational Unit, O=Organization, L=City, ST=State, C=XX", + "-keystore", "juxclient.p12", "-storepass", "clientpass"); + + success &= runCommand("Exporting client certificate (optional, only for client authentication)", // + ("keytool -exportcert -alias juxclient -keystore juxclient.p12 -storepass clientpass -file juxclient.pem") + .split("[ ]+")); + + success &= runCommand( + "Importing client certificate server client truststore (optional, only for client authentication)", // + ("keytool -importcert -alias juxclient -keystore juxserver.truststore -storepass servertrustpass" + + " -file juxclient.pem -noprompt").split("[ ]+")); + + System.out.println("DONE. All successful=" + success); + } +} diff --git a/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/ssl/SSLDemoServer.java b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/ssl/SSLDemoServer.java new file mode 100644 index 000000000..05ebb7db2 --- /dev/null +++ b/junixsocket-demo/src/main/java/org/newsclub/net/unix/demo/ssl/SSLDemoServer.java @@ -0,0 +1,146 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.demo.ssl; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.SNIMatcher; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.security.auth.DestroyFailedException; + +import org.newsclub.net.unix.AFSocket; +import org.newsclub.net.unix.AFUNIXSocket; +import org.newsclub.net.unix.AFUNIXSocketAddress; +import org.newsclub.net.unix.FileDescriptorCast; +import org.newsclub.net.unix.server.AFSocketServer; +import org.newsclub.net.unix.ssl.SNIHostnameCapture; +import org.newsclub.net.unix.ssl.SSLContextBuilder; + +/** + * A simple SSL demo server. + * + * @author Christian Kohlschütter + * @see SSLDemoClient + * @see SSLDemoPrerequisites + */ +@SuppressWarnings("CatchAndPrintStackTrace" /* errorprone */) +public class SSLDemoServer { + public static void main(String[] args) throws InterruptedException, IOException, + GeneralSecurityException, DestroyFailedException { + + // Enable to see SSL debugging + // System.setProperty("javax.net.debug", "all"); + + AFUNIXSocketAddress addr = AFUNIXSocketAddress.of(new File("/tmp/ssldemo")); + + SSLSocketFactory serverSocketFactory = SSLContextBuilder.forServer() // + .withKeyStore(new File("juxserver.p12"), () -> "serverpass".toCharArray()) // + .withTrustStore(new File("juxserver.truststore"), () -> "servertrustpass".toCharArray()) // + .withDefaultSSLParameters((p) -> { + // A simple way to disable some undesired protocols: + // SSLParametersUtil.disableProtocols(p, "TLSv1.0", "TLSv1.1", "TLSv1.2"); + + // Uncomment to require client-authentication + // (which is meaningful in the context of UNIX sockets) + // p.setNeedClientAuth(true); + + // Uncomment to perform endpoint checking of client certificates + // p.setEndpointIdentificationAlgorithm("HTTPS"); + }) // + .buildAndDestroyBuilder().getSocketFactory(); + + AFSocketServer server = new AFSocketServer(addr) { + + // Match any SNI hostname, including none sent + final SNIMatcher sniHostnameMatcher = SNIHostnameCapture.ACCEPT_ANY_HOSTNAME; + + // Replace with code below to enforce receiving a valid SNI hostname + // final SNIMatcher sniHostnameMatcher = SNIHostName.createSNIMatcher("(^|.+?\\.)" + Pattern + // .quote("example.com")); + + @Override + protected void doServeSocket(AFSocket plainSocket) + throws IOException { + try (SSLSocket sslSocket = (SSLSocket) serverSocketFactory.createSocket(plainSocket, + // This is considered the peer name (the client's name) + "localhost.junixsocket", plainSocket.getPort(), // + // you can also replace the above line with: + // null, + false)) { + + // Setting the client mode manually is not necessary when using SSLContextBuilder + // sslSocket.setUseClientMode(false); + + SNIHostnameCapture sniHostname = SNIHostnameCapture.configure(sslSocket, + sniHostnameMatcher); + + // Set to 0 to disable receiving file descriptors, etc. + plainSocket.setAncillaryReceiveBufferSize(1); // rounds up to minimum buffer size + + sslSocket.startHandshake(); + // Make sure the handshake is complete before we check the hostname + // (otherwise: IllegalStateException) + try { + if (sniHostname.isComplete(1, TimeUnit.SECONDS)) { + System.out.println("Requested SNI hostname: " + sniHostname.getHostname()); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + + try (InputStream in = sslSocket.getInputStream(); + OutputStream out = sslSocket.getOutputStream();) { + System.out.println("Received byte: " + Integer.toHexString(in.read())); + + System.out.println("Sending hello..."); + out.write("Hello World".getBytes(StandardCharsets.UTF_8)); + + FileDescriptor[] fds = ((AFUNIXSocket) plainSocket).getReceivedFileDescriptors(); + if (fds.length > 0) { + System.out.println("File descriptor received: " + Arrays.asList(fds)); + System.out.println( + "Sending an extra message directly to a FileDescriptor of the other process..."); + try (PrintStream ps = new PrintStream(FileDescriptorCast.using(fds[0]).as( + OutputStream.class), true, Charset.defaultCharset().name())) { + ps.println("Greetings from the server, right to your stderr"); + } + } + } + } + } + + @Override + protected void onServingException(AFSocket socket, + Exception e) { + e.printStackTrace(); + } + }; + server.startAndWaitToBecomeReady(); + } +} diff --git a/junixsocket-demo/src/main/resources/org/newsclub/net/unix/demo/ssl/juxclient.p12 b/junixsocket-demo/src/main/resources/org/newsclub/net/unix/demo/ssl/juxclient.p12 new file mode 100644 index 000000000..870b0bb64 Binary files /dev/null and b/junixsocket-demo/src/main/resources/org/newsclub/net/unix/demo/ssl/juxclient.p12 differ diff --git a/junixsocket-demo/src/main/resources/org/newsclub/net/unix/demo/ssl/juxclient.pem b/junixsocket-demo/src/main/resources/org/newsclub/net/unix/demo/ssl/juxclient.pem new file mode 100644 index 000000000..0f2f8b017 Binary files /dev/null and b/junixsocket-demo/src/main/resources/org/newsclub/net/unix/demo/ssl/juxclient.pem differ diff --git a/junixsocket-demo/src/main/resources/org/newsclub/net/unix/demo/ssl/juxclient.truststore b/junixsocket-demo/src/main/resources/org/newsclub/net/unix/demo/ssl/juxclient.truststore new file mode 100644 index 000000000..d476eb937 Binary files /dev/null and b/junixsocket-demo/src/main/resources/org/newsclub/net/unix/demo/ssl/juxclient.truststore differ diff --git a/junixsocket-demo/src/main/resources/org/newsclub/net/unix/demo/ssl/juxserver.p12 b/junixsocket-demo/src/main/resources/org/newsclub/net/unix/demo/ssl/juxserver.p12 new file mode 100644 index 000000000..a6ba9f1bd Binary files /dev/null and b/junixsocket-demo/src/main/resources/org/newsclub/net/unix/demo/ssl/juxserver.p12 differ diff --git a/junixsocket-demo/src/main/resources/org/newsclub/net/unix/demo/ssl/juxserver.pem b/junixsocket-demo/src/main/resources/org/newsclub/net/unix/demo/ssl/juxserver.pem new file mode 100644 index 000000000..f1bcac763 Binary files /dev/null and b/junixsocket-demo/src/main/resources/org/newsclub/net/unix/demo/ssl/juxserver.pem differ diff --git a/junixsocket-demo/src/main/resources/org/newsclub/net/unix/demo/ssl/juxserver.truststore b/junixsocket-demo/src/main/resources/org/newsclub/net/unix/demo/ssl/juxserver.truststore new file mode 100644 index 000000000..f0352b0db Binary files /dev/null and b/junixsocket-demo/src/main/resources/org/newsclub/net/unix/demo/ssl/juxserver.truststore differ diff --git a/junixsocket-demo/src/site/site.xml b/junixsocket-demo/src/site/site.xml index 873972284..6d13afab6 100644 --- a/junixsocket-demo/src/site/site.xml +++ b/junixsocket-demo/src/site/site.xml @@ -7,6 +7,7 @@ org.apache.maven.skins maven-fluido-skin + 2.0.0-M8 diff --git a/junixsocket-dist/pom.xml b/junixsocket-dist/pom.xml index 941f9937a..a93f6c260 100644 --- a/junixsocket-dist/pom.xml +++ b/junixsocket-dist/pom.xml @@ -8,7 +8,7 @@ com.kohlschutter.junixsocket junixsocket - 2.6.1 + 2.10.1 ../pom.xml junixsocket-dist @@ -22,18 +22,15 @@ com.kohlschutter.junixsocket junixsocket-core - ${project.version} pom com.kohlschutter.junixsocket junixsocket-rmi - ${project.version} com.kohlschutter.junixsocket junixsocket-mysql - ${project.version} com.kohlschutter.junixsocket @@ -43,17 +40,14 @@ com.kohlschutter.junixsocket junixsocket-tipc - ${project.version} com.kohlschutter.junixsocket junixsocket-jetty - ${project.version} com.kohlschutter.junixsocket junixsocket-selftest - ${project.version} jar-with-dependencies @@ -61,12 +55,16 @@ release + + + release + + org.apache.maven.plugins maven-assembly-plugin - 3.1.1 build-dist diff --git a/junixsocket-dist/src/site/site.xml b/junixsocket-dist/src/site/site.xml index 873972284..6d13afab6 100644 --- a/junixsocket-dist/src/site/site.xml +++ b/junixsocket-dist/src/site/site.xml @@ -7,6 +7,7 @@ org.apache.maven.skins maven-fluido-skin + 2.0.0-M8 diff --git a/junixsocket-jetty-11/pom.xml b/junixsocket-jetty-11/pom.xml new file mode 100644 index 000000000..92554a7b4 --- /dev/null +++ b/junixsocket-jetty-11/pom.xml @@ -0,0 +1,90 @@ + + + 4.0.0 + junixsocket-jetty-11 + jar + + com.kohlschutter.junixsocket + junixsocket + 2.10.1 + ../pom.xml + + junixsocket-jetty-11 + + ${project.parent.basedir} + + 11.0.21 + + + junixsocket for Jetty 11 (currently test-only; use + junixsocket-jetty as a dependency) + + + + com.kohlschutter.junixsocket + junixsocket-core + pom + + + + com.kohlschutter.junixsocket + junixsocket-jetty + test + + + org.eclipse.jetty + jetty-server + + + + + + org.eclipse.jetty + jetty-server + ${jetty11.version} + provided + + + org.eclipse.jetty + jetty-client + ${jetty11.version} + test + + + + + + org.slf4j + slf4j-simple + test + + + com.squareup.okhttp3 + okhttp + 4.12.0 + test + + + com.kohlschutter.junixsocket + junixsocket-common + tests + test + + + + org.hamcrest + hamcrest + 2.2 + test + + + diff --git a/junixsocket-jetty-11/src/test/java/org/newsclub/net/unix/jetty/AFSocketServerConnectorTest.java b/junixsocket-jetty-11/src/test/java/org/newsclub/net/unix/jetty/AFSocketServerConnectorTest.java new file mode 100644 index 000000000..e5b0a4496 --- /dev/null +++ b/junixsocket-jetty-11/src/test/java/org/newsclub/net/unix/jetty/AFSocketServerConnectorTest.java @@ -0,0 +1,122 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.jetty; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.BufferedReader; +import java.time.Duration; + +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.newsclub.net.unix.AFSocketAddress; +import org.newsclub.net.unix.AFSocketFactory; +import org.newsclub.net.unix.domain.AFUNIXAddressSpecifics; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class AFSocketServerConnectorTest { + @BeforeAll + public static void setUp() { + System.setProperty("org.slf4j.simpleLogger.log.org.eclipse.jetty.server", "error"); + } + + @Test + public void testServerAFUNIX() throws Exception { + AFSocketAddress addr = (AFSocketAddress) AFUNIXAddressSpecifics.INSTANCE + .newTempAddressForDatagram(); + Server server1 = newServer(addr); + checkConnection(addr); + assertTrue(server1.isRunning()); + + Server server2 = newServer(addr); + + // isDeleteOnClose is smart enough to not delete the wrong socket + assertTrue(addr.getFile().exists()); + + server1.join(); + assertFalse(server1.isRunning()); + server2.stop(); + server2.join(); + assertFalse(addr.getFile().exists()); // isDeleteOnClose=true by default + } + + private void checkConnection(AFSocketAddress addr) throws Exception { + OkHttpClient.Builder builder = new OkHttpClient.Builder() // + .socketFactory(new AFSocketFactory.FixedAddressSocketFactory(addr)) // + .callTimeout(Duration.ofMinutes(1)); + + OkHttpClient client = builder.build(); + + Request request = new Request.Builder().url("http://localhost/").build(); + try (Response response = client.newCall(request).execute()) { + assertEquals(404, response.code()); + assertNotNull(response.header("Server")); + + ResponseBody body = response.body(); + assertNotNull(body); + + BufferedReader br = new BufferedReader(body.charStream()); + int l = 0; + while (br.readLine() != null) { + l++; + } + assertNotEquals(0, l); + } + } + + private static Server newServer(AFSocketAddress addr) throws Exception { + Server server = new Server(); + + // below code is based upon + // https://www.eclipse.org/jetty/documentation/jetty-10/programming-guide/index.html + + // The number of acceptor threads. + int acceptors = 1; + + // The number of selectors. + int selectors = 1; + + // Create a ServerConnector instance. + AFSocketServerConnector connector = new AFSocketServerConnector(server, acceptors, selectors, + new HttpConnectionFactory()); + + // The AFSocketAddress to listen to. + connector.setListenSocketAddress(addr); + + // The accept queue size. + connector.setAcceptQueueSize(128); + + // Try to automatically stop server if another instance reuses our address + connector.setMayStopServer(true); + + server.addConnector(connector); + server.start(); + + return server; + } +} diff --git a/junixsocket-jetty/src/test/java/org/eclipse/jetty/unixdomain/server/UnixDomainTest.java b/junixsocket-jetty-11/src/test/java/org/newsclub/net/unix/jetty/UnixDomainTest.java similarity index 88% rename from junixsocket-jetty/src/test/java/org/eclipse/jetty/unixdomain/server/UnixDomainTest.java rename to junixsocket-jetty-11/src/test/java/org/newsclub/net/unix/jetty/UnixDomainTest.java index 78b2027e5..725f1594e 100644 --- a/junixsocket-jetty/src/test/java/org/eclipse/jetty/unixdomain/server/UnixDomainTest.java +++ b/junixsocket-jetty-11/src/test/java/org/newsclub/net/unix/jetty/UnixDomainTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,9 +28,10 @@ // ======================================================================== // -package org.eclipse.jetty.unixdomain.server; +package org.newsclub.net.unix.jetty; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -45,6 +46,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; +import java.util.Random; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.client.HttpClient; @@ -72,9 +74,8 @@ import org.junit.jupiter.api.Test; import org.newsclub.net.unix.AFSocketAddress; import org.newsclub.net.unix.AFUNIXSocketAddress; -import org.newsclub.net.unix.jetty.AFSocketClientConnector; -import org.newsclub.net.unix.jetty.AFSocketServerConnector; +import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -193,7 +194,7 @@ public void handle(String target, Request jettyRequest, HttpServletRequest reque .of(unixDomainPath)); HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector)); - httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", fakeProxyPort)); + httpClient.getProxyConfiguration().addProxy(new HttpProxy("localhost", fakeProxyPort)); httpClient.start(); try { ContentResponse response = httpClient.newRequest("localhost", fakeServerPort).timeout(5, @@ -308,7 +309,8 @@ private static Path toUnixDomainPath(SocketAddress address) { public static String separators(String path) { StringBuilder ret = new StringBuilder(); - for (char c : path.toCharArray()) { + for (int i = 0, n = path.length(); i < n; i++) { + char c = path.charAt(i); if ((c == '/') || (c == '\\')) { ret.append(File.separatorChar); } else { @@ -317,4 +319,40 @@ public static String separators(String path) { } return ret.toString(); } + + @Test + public void testLargeBody() throws Exception { + String uri = "http://localhost:1234/path"; + + byte[] payload = new byte[512 * 1024]; // 512k + new Random().nextBytes(payload); + + start(new AbstractHandler() { + @Override + public void handle(String target, Request jettyRequest, HttpServletRequest request, + HttpServletResponse response) throws IOException { + jettyRequest.setHandled(true); + + response.setContentType(" application/octet-stream"); + try (ServletOutputStream out = response.getOutputStream()) { + out.write(payload); + } + } + }); + + // ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath); + ClientConnector clientConnector = AFSocketClientConnector.withSocketAddress(AFUNIXSocketAddress + .of(unixDomainPath)); + HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector)); + httpClient.start(); + try { + ContentResponse response = httpClient.newRequest(uri).timeout(5, TimeUnit.SECONDS).send(); + + assertEquals(HttpStatus.OK_200, response.getStatus()); + byte[] data = response.getContent(); + assertArrayEquals(payload, data); + } finally { + httpClient.stop(); + } + } } \ No newline at end of file diff --git a/junixsocket-jetty/pom.xml b/junixsocket-jetty/pom.xml index be05bc148..abc83fc60 100644 --- a/junixsocket-jetty/pom.xml +++ b/junixsocket-jetty/pom.xml @@ -1,61 +1,81 @@ - + 4.0.0 junixsocket-jetty jar com.kohlschutter.junixsocket junixsocket - 2.6.1 + 2.10.1 ../pom.xml junixsocket-jetty ${project.parent.basedir} - 11.0.12 junixsocket for Jetty + + + + + org.codehaus.mojo + versions-maven-plugin + + + false + + + + + + com.kohlschutter.junixsocket junixsocket-core - ${project.version} pom + org.eclipse.jetty jetty-server - ${jetty.version} provided + + org.eclipse.jetty + jetty-client + test + + org.slf4j slf4j-simple - 2.0.3 test com.squareup.okhttp3 okhttp - 4.10.0 + 4.12.0 test com.kohlschutter.junixsocket junixsocket-common - ${project.version} tests test - - org.eclipse.jetty - jetty-client - ${jetty.version} - test - + org.hamcrest hamcrest @@ -63,4 +83,4 @@ test - \ No newline at end of file + diff --git a/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/AFSocketClientConnector.java b/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/AFSocketClientConnector.java index 5dee06881..9692e89d9 100644 --- a/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/AFSocketClientConnector.java +++ b/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/AFSocketClientConnector.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,11 +18,8 @@ package org.newsclub.net.unix.jetty; import java.io.IOException; -import java.net.SocketAddress; import java.nio.channels.Selector; -import java.nio.channels.SocketChannel; import java.nio.channels.spi.SelectorProvider; -import java.util.Map; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.SelectorManager; @@ -32,16 +29,17 @@ /** * A {@link Connector} implementation for junixsocket server socket channels (Unix domains etc.) - * + * * Based upon jetty's ClientConnector. - * + * * This implementation should work with jetty version 10.0.8 or newer. - * + * * @author Christian Kohlschütter */ public final class AFSocketClientConnector extends ClientConnector { private final AFAddressFamily addressFamily; + @SuppressWarnings("removal") private AFSocketClientConnector(AFSocketAddress addr) { super(configuratorFor(addr)); this.addressFamily = addr.getAddressFamily(); @@ -50,7 +48,7 @@ private AFSocketClientConnector(AFSocketAddress addr) { /** * Returns a new {@link ClientConnector} configured to use given {@link AFSocketAddress} for * communication with junixsocket sockets. - * + * * @param addr The socket address. * @return The client connector. */ @@ -69,14 +67,12 @@ protected Selector newSelector() throws IOException { }; } + @SuppressWarnings({"removal", "deprecation"}) private static Configurator configuratorFor(AFSocketAddress addr) { - return new Configurator() { - @Override - public ChannelWithAddress newChannelWithAddress(ClientConnector clientConnector, - SocketAddress address, Map context) throws IOException { - SocketChannel socketChannel = addr.getAddressFamily().newSocketChannel(); - return new ChannelWithAddress(socketChannel, addr); - } - }; + if (JettyCompat.hasTransportClass()) { + return new AFSocketConfiguratorWithTransport(addr); + } else { + return new AFSocketConfigurator(addr); + } } } diff --git a/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/AFSocketConfigurator.java b/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/AFSocketConfigurator.java new file mode 100644 index 000000000..1a04dd2e7 --- /dev/null +++ b/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/AFSocketConfigurator.java @@ -0,0 +1,49 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.jetty; + +import java.io.IOException; +import java.net.SocketAddress; +import java.nio.channels.SocketChannel; +import java.util.Map; + +import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.io.ClientConnector.Configurator; +import org.newsclub.net.unix.AFSocketAddress; + +/** + * A {@link ClientConnector.Configurator} for junixsocket {@link SocketChannel}s. + * + * @author Christian Kohlschütter + */ +@Deprecated +class AFSocketConfigurator extends Configurator { + protected final AFSocketAddress addr; + + AFSocketConfigurator(AFSocketAddress addr) { + super(); + this.addr = addr; + } + + @Override + public ChannelWithAddress newChannelWithAddress(ClientConnector clientConnector, + SocketAddress address, Map context) throws IOException { + SocketChannel socketChannel = addr.getAddressFamily().newSocketChannel(); + return new ChannelWithAddress(socketChannel, addr); + } +} diff --git a/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/AFSocketConfiguratorWithTransport.java b/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/AFSocketConfiguratorWithTransport.java new file mode 100644 index 000000000..0116c4293 --- /dev/null +++ b/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/AFSocketConfiguratorWithTransport.java @@ -0,0 +1,42 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.jetty; + +import java.nio.channels.SocketChannel; + +import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.io.Transport; +import org.newsclub.net.unix.AFSocketAddress; + +/** + * A {@link ClientConnector.Configurator} for junixsocket {@link SocketChannel}s, as required by + * jetty 12.0.7. + * + * @author Christian Kohlschütter + */ +@Deprecated +final class AFSocketConfiguratorWithTransport extends AFSocketConfigurator { + AFSocketConfiguratorWithTransport(AFSocketAddress addr) { + super(addr); + } + + @Override + public Transport newTransport() { + return new AFSocketTransport.WithSocketChannel(addr); + } +} diff --git a/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/AFSocketServerConnector.java b/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/AFSocketServerConnector.java index 7991b8186..f2e34c85e 100644 --- a/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/AFSocketServerConnector.java +++ b/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/AFSocketServerConnector.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,11 +50,9 @@ import java.util.EventListener; import java.util.Locale; import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.ForkJoinPool; import java.util.concurrent.atomic.AtomicReference; -import org.eclipse.jetty.io.ArrayByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; @@ -81,12 +79,13 @@ /** * A {@link Connector} implementation for junixsocket server socket channels (Unix domains etc.) - * + * * Based upon jetty's UnixDomainServerConnector. - * + * * This implementation should work with jetty version 9.4.12 or newer. */ @ManagedObject +@SuppressWarnings("PMD.CouplingBetweenObjects") public class AFSocketServerConnector extends AbstractConnector { private static final Logger LOG = LoggerFactory.getLogger(AbstractConnector.class); @@ -100,12 +99,13 @@ public class AFSocketServerConnector extends AbstractConnector { private int acceptedSendBufferSize; private boolean mayStopServer = false; + private boolean mayStopServerForce = false; private final Class selectorManagerListenerClass; private final Server server; /** * Creates a new {@link AFSocketServerConnector}. - * + * * @param server The server this connector will be added to. Must not be null. * @param factories The Connection Factories to use. */ @@ -115,7 +115,7 @@ public AFSocketServerConnector(Server server, ConnectionFactory... factories) { /** * Creates a new {@link AFSocketServerConnector}. - * + * * @param server The server this connector will be added to. Must not be null. * @param acceptors the number of acceptor threads to use, or -1 for a default value. If 0, then * no acceptor threads will be launched and some other mechanism will need to be used to @@ -130,13 +130,13 @@ public AFSocketServerConnector(Server server, int acceptors, int selectors, /** * Creates a new {@link AFSocketServerConnector}. - * + * * @param server The server this connector will be added to. Must not be null. * @param executor An executor for this connector or null to use the servers executor * @param scheduler A scheduler for this connector or null to either a {@link Scheduler} set as a * server bean or if none set, then a new {@link ScheduledExecutorScheduler} instance. * @param pool A buffer pool for this connector or null to either a {@link ByteBufferPool} set as - * a server bean or none set, the new {@link ArrayByteBufferPool} instance. + * a server bean or none set, the new {code ArrayByteBufferPool} instance. * @param acceptors the number of acceptor threads to use, or -1 for a default value. If 0, then * no acceptor threads will be launched and some other mechanism will need to be used to * accept new connections. @@ -144,6 +144,7 @@ public AFSocketServerConnector(Server server, int acceptors, int selectors, * @param factories The Connection Factories to use. */ @SuppressFBWarnings("EI_EXPOSE_REP2") + @SuppressWarnings("PMD.ConstructorCallsOverridableMethod") public AFSocketServerConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, int selectors, ConnectionFactory... factories) { super(server, executor, scheduler, pool, acceptors, factories.length > 0 ? factories @@ -172,14 +173,15 @@ private SelectorManager newSelectorManager(Executor executor, Scheduler schedule /** * Returns the Unix-Domain path this connector listens to. - * + * * Added for compatibility with jetty's {@code UnixDomainServerConnector}. - * + * * @return The Unix-Domain path this connector listens to. * @deprecated Use {@link #getListenSocketAddress()} instead. * @see #getListenSocketAddress() */ @ManagedAttribute("The Unix-Domain path this connector listens to") + @Deprecated public Path getUnixDomainPath() { if (listenSocketAddress instanceof AFUNIXSocketAddress) { AFUNIXSocketAddress addr = (AFUNIXSocketAddress) listenSocketAddress; @@ -196,13 +198,14 @@ public Path getUnixDomainPath() { /** * Sets the Unix-Domain path this connector listens to. - * + * * Added for compatibility with jetty's {@code UnixDomainServerConnector}. - * + * * @param unixDomainPath The path. * @deprecated Use {@link #setListenSocketAddress(AFSocketAddress)} instead. * @see #setListenSocketAddress(AFSocketAddress) */ + @Deprecated public void setUnixDomainPath(Path unixDomainPath) { try { this.listenSocketAddress = AFUNIXSocketAddress.of(unixDomainPath); @@ -213,7 +216,7 @@ public void setUnixDomainPath(Path unixDomainPath) { /** * Returns the socket address this connector listens to. - * + * * @return The socket address, or {@code null} if none set. */ @ManagedAttribute("The socket address this connector listens to") @@ -224,7 +227,7 @@ public AFSocketAddress getListenSocketAddress() { /** * Sets the socket address this connector listens to. - * + * * @param addr The socket address, or {@code null}. */ @SuppressFBWarnings("EI_EXPOSE_REP2") @@ -234,7 +237,7 @@ public void setListenSocketAddress(AFSocketAddress addr) { /** * Checks whether this connector uses a server channel inherited from the JVM. - * + * * @return {@code true} if so. */ @ManagedAttribute("Whether this connector uses a server channel inherited from the JVM") @@ -244,7 +247,7 @@ public boolean isInheritChannel() { /** * Sets whether this connector uses a server channel inherited from the JVM. - * + * * @param inheritChannel {@code true} if so. */ public void setInheritChannel(boolean inheritChannel) { @@ -253,7 +256,7 @@ public void setInheritChannel(boolean inheritChannel) { /** * Returns the accept queue size (backlog) for the server socket. - * + * * @return The backlog. */ @ManagedAttribute("The accept queue size (backlog) for the server socket") @@ -263,7 +266,7 @@ public int getAcceptQueueSize() { /** * Sets the accept queue size (backlog) for the server socket. - * + * * @param acceptQueueSize The backlog. */ public void setAcceptQueueSize(int acceptQueueSize) { @@ -272,7 +275,7 @@ public void setAcceptQueueSize(int acceptQueueSize) { /** * Returns the SO_RCVBUF size for accepted sockets. - * + * * @return The buffer size. */ @ManagedAttribute("The SO_RCVBUF option for accepted sockets") @@ -282,7 +285,7 @@ public int getAcceptedReceiveBufferSize() { /** * Sets the SO_RCVBUF size for accepted sockets. - * + * * @param acceptedReceiveBufferSize The buffer size. */ public void setAcceptedReceiveBufferSize(int acceptedReceiveBufferSize) { @@ -291,7 +294,7 @@ public void setAcceptedReceiveBufferSize(int acceptedReceiveBufferSize) { /** * Returns the SO_SNDBUF size for accepted sockets. - * + * * @return The buffer size. */ @ManagedAttribute("The SO_SNDBUF option for accepted sockets") @@ -301,7 +304,7 @@ public int getAcceptedSendBufferSize() { /** * Sets the SO_SNDBUF size for accepted sockets. - * + * * @param acceptedSendBufferSize The buffer size. */ public void setAcceptedSendBufferSize(int acceptedSendBufferSize) { @@ -336,51 +339,44 @@ protected void accept(int acceptorID) throws IOException { try { SocketChannel channel = sc.accept(); accepted(channel); - } catch (SocketException e) { + } catch (IOException e) { boolean takenOver = !sc.isOpen() || sc.getLocalAddress() == null; + if (!takenOver && sc instanceof AFServerSocketChannel) { takenOver = !((AFServerSocketChannel) sc).isLocalSocketAddressValid(); } - if (takenOver) { - ExecutorService es = Executors.newSingleThreadExecutor(); - try { - LOG.warn("Another server has taken over our address"); - es.execute(() -> { - Connector[] connectors = server.getConnectors(); - - boolean shutdownServer; - if (connectors == null) { - shutdownServer = true; - } else { - shutdownServer = true; - for (Connector conn : connectors) { - if (conn != AFSocketServerConnector.this && conn.isRunning()) { // NOPMD.CompareObjectsWithEquals - shutdownServer = false; - break; - } - } - } - - if (shutdownServer && mayStopServer) { - LOG.warn("Server has no other connectors; shutting down: " + server); // NOPMD - - try { - server.stop(); - } catch (Exception e1) { - LOG.warn("Exception upon stopping " + server, e1); // NOPMD - } - } - }); - } finally { - es.shutdown(); - } + if (takenOver && isMayStopServer()) { + LOG.warn("Another server has taken over our address"); + ForkJoinPool.commonPool().execute(this::checkServerStop); } + + Thread.currentThread().interrupt(); throw (ClosedByInterruptException) new ClosedByInterruptException().initCause(e); } } } + private void checkServerStop() { + Connector[] connectors = server.getConnectors(); + + if (connectors != null && !isMayStopServerForce()) { + for (Connector conn : connectors) { + if (conn != AFSocketServerConnector.this && conn.isRunning()) { // NOPMD.CompareObjectsWithEquals + return; // don't stop + } + } + } + + LOG.warn("Server has no other connectors; shutting down: " + server); // NOPMD + + try { + server.stop(); + } catch (Exception e1) { + LOG.warn("Exception upon stopping " + server, e1); // NOPMD + } + } + private void accepted(SocketChannel channel) throws IOException { channel.configureBlocking(false); configure(channel); @@ -390,7 +386,7 @@ private void accepted(SocketChannel channel) throws IOException { /** * Configures an incoming {@link SocketChannel}, setting socket options such as receive and send * buffer sizes. - * + * * @param channel The socket channel to configure. * @throws IOException on error. */ @@ -533,7 +529,7 @@ protected void endPointClosed(EndPoint endpoint) { /** * Checks if this connector may stop the server when it's no longer able to serve and no other * connectors are available. - * + * * @return {@code true} if so. */ @ManagedAttribute("Whether this connector may stop the server when it's no longer able to" @@ -545,10 +541,35 @@ public boolean isMayStopServer() { /** * Sets if this connector may stop the server when it's no longer able to serve and no other * connectors are available. - * + * * @param mayStopServer {@code true} if so. */ public void setMayStopServer(boolean mayStopServer) { this.mayStopServer = mayStopServer; } + + /** + * Checks if this connector may stop the server when it's no longer able to serve, even if other + * connectors are available. + * + * @return {@code true} if so. + */ + @ManagedAttribute("Whether this connector may stop the server when it's no longer able to" + + " serve, even if other connectors are available") + public boolean isMayStopServerForce() { + return mayStopServerForce; + } + + /** + * Sets if this connector may stop the server when it's no longer able to serve and no other + * connectors are available. + * + * @param b {@code true} if so (which then also implies {@code setMayStopServer(true)} + */ + public void setMayStopServerForce(boolean b) { + if (b) { + setMayStopServer(true); + } + this.mayStopServerForce = b; + } } diff --git a/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/AFSocketTransport.java b/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/AFSocketTransport.java new file mode 100644 index 000000000..3ef501dd1 --- /dev/null +++ b/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/AFSocketTransport.java @@ -0,0 +1,134 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.jetty; + +import java.io.IOException; +import java.nio.channels.DatagramChannel; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.Objects; + +import org.eclipse.jetty.io.DatagramChannelEndPoint; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.ManagedSelector; +import org.eclipse.jetty.io.SocketChannelEndPoint; +import org.eclipse.jetty.io.Transport; +import org.eclipse.jetty.util.thread.Scheduler; +import org.newsclub.net.unix.AFSocketAddress; + +/** + * A {@link Transport} implementation for junixsocket socket and datagram channels (Unix domains + * etc.) + *

+ * This implementation should work with jetty version 12.0.7 or newer. + * + * @author Christian Kohlschütter + */ +public class AFSocketTransport extends Transport.Socket { + private final AFSocketAddress socketAddress; + + private AFSocketTransport(AFSocketAddress socketAddress) { + super(); + this.socketAddress = socketAddress; + } + + /** + * Constructs an {@link AFSocketTransport} that establishes a {@link SocketChannel} to the given + * address. + * + * @param addr The target address. + * @return The {@link Transport} instance. + */ + public static AFSocketTransport withSocketChannel(AFSocketAddress addr) { + return new WithSocketChannel(addr); + } + + /** + * Constructs an {@link AFSocketTransport} that establishes a {@link DatagramChannel} to the given + * address. + * + * @param addr The target address. + * @return The {@link Transport} instance. + */ + public static AFSocketTransport withDatagramChannel(AFSocketAddress addr) { + return new WithDatagramChannel(addr); + } + + @Override + public AFSocketAddress getSocketAddress() { + return socketAddress; + } + + @Override + public int hashCode() { + return Objects.hash(socketAddress); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof AFSocketTransport) { + return Objects.equals(socketAddress, ((AFSocketTransport) obj).socketAddress); + } + return false; + } + + @Override + public String toString() { + return super.toString() + "[" + socketAddress.toString() + "]"; + } + + static class WithSocketChannel extends AFSocketTransport { + public WithSocketChannel(AFSocketAddress address) { + super(address); + } + + @Override + public SelectableChannel newSelectableChannel() throws IOException { + return getSocketAddress().getAddressFamily().getSelectorProvider().openSocketChannel(); + } + + @Override + public EndPoint newEndPoint(Scheduler scheduler, ManagedSelector selector, + SelectableChannel selectable, SelectionKey selectionKey) { + return new SocketChannelEndPoint((SocketChannel) selectable, selector, selectionKey, + scheduler); + } + } + + static class WithDatagramChannel extends AFSocketTransport { + public WithDatagramChannel(AFSocketAddress address) { + super(address); + } + + @Override + public SelectableChannel newSelectableChannel() throws IOException { + return getSocketAddress().getAddressFamily().getSelectorProvider().openDatagramChannel(); + } + + @Override + public EndPoint newEndPoint(Scheduler scheduler, ManagedSelector selector, + SelectableChannel selectable, SelectionKey selectionKey) { + return new DatagramChannelEndPoint((DatagramChannel) selectable, selector, selectionKey, + scheduler); + } + } +} diff --git a/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/JettyCompat.java b/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/JettyCompat.java new file mode 100644 index 000000000..f1b6d4104 --- /dev/null +++ b/junixsocket-jetty/src/main/java/org/newsclub/net/unix/jetty/JettyCompat.java @@ -0,0 +1,41 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.unix.jetty; + +final class JettyCompat { + private static final boolean HAVE_TRANSPORT_CLASS; + + static { + boolean found; + try { + Class.forName("org.eclipse.jetty.io.Transport"); + found = true; + } catch (Exception e) { // NOPMD + found = false; + } + HAVE_TRANSPORT_CLASS = found; + } + + private JettyCompat() { + throw new IllegalStateException("No instances"); + } + + static boolean hasTransportClass() { + return HAVE_TRANSPORT_CLASS; + } +} diff --git a/junixsocket-jetty/src/site/site.xml b/junixsocket-jetty/src/site/site.xml index 873972284..6d13afab6 100644 --- a/junixsocket-jetty/src/site/site.xml +++ b/junixsocket-jetty/src/site/site.xml @@ -7,6 +7,7 @@ org.apache.maven.skins maven-fluido-skin + 2.0.0-M8 diff --git a/junixsocket-jetty/src/test/java/org/newsclub/net/unix/jetty/AFSocketServerConnectorTest.java b/junixsocket-jetty/src/test/java/org/newsclub/net/unix/jetty/AFSocketServerConnectorTest.java index 7f281526f..e5b0a4496 100644 --- a/junixsocket-jetty/src/test/java/org/newsclub/net/unix/jetty/AFSocketServerConnectorTest.java +++ b/junixsocket-jetty/src/test/java/org/newsclub/net/unix/jetty/AFSocketServerConnectorTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-jetty/src/test/java/org/newsclub/net/unix/jetty/UnixDomainTest.java b/junixsocket-jetty/src/test/java/org/newsclub/net/unix/jetty/UnixDomainTest.java new file mode 100644 index 000000000..54dafd8d3 --- /dev/null +++ b/junixsocket-jetty/src/test/java/org/newsclub/net/unix/jetty/UnixDomainTest.java @@ -0,0 +1,366 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.newsclub.net.unix.jetty; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.client.ContentResponse; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpProxy; +import org.eclipse.jetty.client.Origin; +import org.eclipse.jetty.client.ProxyProtocolClientConnectionFactory.V1; +import org.eclipse.jetty.client.ProxyProtocolClientConnectionFactory.V2; +import org.eclipse.jetty.client.transport.HttpClientTransportDynamic; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.ConnectionMetaData; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.ProxyConnectionFactory; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.component.LifeCycle; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.newsclub.net.unix.AFSocketAddress; +import org.newsclub.net.unix.AFUNIXSocketAddress; + +public class UnixDomainTest { + private static final Class unixDomainSocketAddressClass = probe(); + + private static Class probe() { + try { + return ClassLoader.getPlatformClassLoader().loadClass("java.net.UnixDomainSocketAddress"); + } catch (Throwable x) { + return null; + } + } + + private ConnectionFactory[] factories = new ConnectionFactory[] {new HttpConnectionFactory()}; + private Server server; + private Path unixDomainPath; + + @BeforeAll + public static void setUp() { + System.setProperty("org.slf4j.simpleLogger.log.org.eclipse.jetty", "error"); + } + + private void start(Handler handler) throws Exception { + server = new Server(); + // UnixDomainServerConnector connector = new UnixDomainServerConnector(server, factories); + AFSocketServerConnector connector = new AFSocketServerConnector(server, factories); + String dir = System.getProperty("jetty.unixdomain.dir", System.getProperty("java.io.tmpdir")); + assertNotNull(dir); + File dirFile = new File(dir); + dirFile.mkdirs(); + unixDomainPath = Files.createTempFile(dirFile.toPath(), "unix_", ".sock"); + assertTrue(unixDomainPath.toAbsolutePath().toString().length() < 108, + "Unix-Domain path too long"); + Files.delete(unixDomainPath); + // connector.setUnixDomainPath(unixDomainPath); + connector.setListenSocketAddress(AFUNIXSocketAddress.of(unixDomainPath)); + server.addConnector(connector); + server.setHandler(handler); + server.start(); + } + + @AfterEach + public void dispose() { + LifeCycle.stop(server); + } + + @Test + public void testHTTPOverUnixDomain() throws Exception { + String uri = "http://localhost:1234/path"; + start(new Handler.Abstract() { + + @Override + public boolean handle(Request request, Response response, Callback callback) + throws Exception { + // Verify the URI is preserved. + assertEquals(uri, request.getHttpURI().toString()); + + // Verify the SocketAddresses. + ConnectionMetaData connectionMetaData = request.getConnectionMetaData(); + SocketAddress local = connectionMetaData.getLocalSocketAddress(); + SocketAddress remote = connectionMetaData.getRemoteSocketAddress(); + + assertThat(local, Matchers.instanceOf(AFSocketAddress.class)); + if (remote != null) { + // remote should be null if not connected + assertThat(remote, Matchers.instanceOf(AFSocketAddress.class)); + } + + assertDoesNotThrow(connectionMetaData::toString); + + response.write(true, ByteBuffer.allocate(0), new Callback() { + }); + + return true; + } + }); + + // ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath); + ClientConnector clientConnector = AFSocketClientConnector.withSocketAddress(AFUNIXSocketAddress + .of(unixDomainPath)); + HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector)); + httpClient.start(); + try { + ContentResponse response = httpClient.newRequest(uri).timeout(5, TimeUnit.SECONDS).send(); + + assertEquals(HttpStatus.OK_200, response.getStatus()); + } finally { + httpClient.stop(); + } + } + + @Test + public void testHTTPOverUnixDomainWithHTTPProxy() throws Exception { + int fakeProxyPort = 4567; + int fakeServerPort = 5678; + start(new Handler.Abstract() { + @Override + public boolean handle(Request request, Response response, Callback callback) + throws Exception { + // Proxied requests must have an absolute URI. + HttpURI uri = request.getHttpURI(); + assertNotNull(uri.getScheme()); + assertEquals(fakeServerPort, uri.getPort()); + + response.write(true, ByteBuffer.allocate(0), new Callback() { + }); + + return true; + } + }); + + // ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath); + + AFUNIXSocketAddress unixDomainAddress = AFUNIXSocketAddress.of(unixDomainPath); + ClientConnector clientConnector = AFSocketClientConnector.withSocketAddress(unixDomainAddress); + + // HttpProxy proxy = new HttpProxy("localhost", fakeProxyPort); // this worked until 12.0.6 + HttpProxy proxy = new HttpProxy(new Origin("http", new Origin.Address("localhost", + fakeProxyPort), null, new Origin.Protocol(List.of("http/1.1"), false), AFSocketTransport + .withSocketChannel(unixDomainAddress)), null); + + HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector)); + httpClient.getProxyConfiguration().addProxy(proxy); + httpClient.start(); + try { + ContentResponse response = httpClient.newRequest("localhost", fakeServerPort).timeout(5, + TimeUnit.SECONDS).send(); + + assertEquals(HttpStatus.OK_200, response.getStatus()); + } finally { + httpClient.stop(); + } + } + + @Test + public void testHTTPOverUnixDomainWithProxyProtocol() throws Exception { + String srcAddr = "/proxySrcAddr"; + String dstAddr = "/proxyDstAddr"; + factories = new ConnectionFactory[] {new ProxyConnectionFactory(), new HttpConnectionFactory()}; + start(new Handler.Abstract() { + @Override + public boolean handle(Request request, Response response, Callback callback) + throws Exception { + ConnectionMetaData connectionMetaData = request.getConnectionMetaData(); + + String target = request.getHttpURI().getPath(); + + if ("/v1".equals(target)) { + // As PROXYv1 does not support UNIX, the wrapped EndPoint data is used. + Path localPath = toUnixDomainPath(connectionMetaData.getLocalSocketAddress()); + assertThat(localPath, Matchers.equalTo(unixDomainPath)); + } else if ("/v2".equals(target)) { + SocketAddress localSocketAddress = connectionMetaData.getLocalSocketAddress(); + if (localSocketAddress != null) { + assertThat(toUnixDomainPath(localSocketAddress).toString(), Matchers.equalTo(separators( + dstAddr))); + } + SocketAddress remoteSocketAddress = connectionMetaData.getRemoteSocketAddress(); + if (remoteSocketAddress != null) { + assertThat(toUnixDomainPath(remoteSocketAddress).toString(), Matchers.equalTo( + separators(srcAddr))); + } + } else { + Assertions.fail("Invalid PROXY protocol version " + target); + } + + response.write(true, ByteBuffer.allocate(0), new Callback() { + }); + + return true; + } + }); + + // Java 11+ portable way to implement SocketChannelWithAddress.Factory. + // ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath); + ClientConnector clientConnector = AFSocketClientConnector.withSocketAddress(AFUNIXSocketAddress + .of(unixDomainPath)); + + HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector)); + httpClient.start(); + try { + // Try PROXYv1 with the PROXY information retrieved from the EndPoint. + // PROXYv1 does not support the UNIX family. + ContentResponse response1 = httpClient.newRequest("localhost", 0).path("/v1").tag( + new V1.Tag()).timeout(5, TimeUnit.SECONDS).send(); + + assertEquals(HttpStatus.OK_200, response1.getStatus()); + + // Try PROXYv2 with explicit PROXY information. + V2.Tag tag = new V2.Tag(V2.Tag.Command.PROXY, V2.Tag.Family.UNIX, V2.Tag.Protocol.STREAM, + srcAddr, 0, dstAddr, 0, null); + ContentResponse response2 = httpClient.newRequest("localhost", 0).path("/v2").tag(tag) + .timeout(5, TimeUnit.SECONDS).send(); + + assertEquals(HttpStatus.OK_200, response2.getStatus()); + } finally { + httpClient.stop(); + } + } + + @Test + public void testInvalidUnixDomainPath() { + server = new Server(); + // UnixDomainServerConnector connector = new UnixDomainServerConnector(server, factories); + AFSocketServerConnector connector = new AFSocketServerConnector(server, factories); + + // connector.setUnixDomainPath(new File("/does/not/exist").toPath()); + try { + connector.setListenSocketAddress(AFUNIXSocketAddress.of(new File("/does/not/exist") + .toPath())); + } catch (SocketException e) { + throw new IllegalStateException(e); + } + + server.addConnector(connector); + assertThrows(IOException.class, () -> server.start()); + } + + private static Path toUnixDomainPath(SocketAddress address) { + Objects.requireNonNull(address, "address"); + + if (address instanceof AFUNIXSocketAddress) { + return new File(((AFUNIXSocketAddress) address).getPath()).toPath(); + } else if (unixDomainSocketAddressClass != null) { + try { + return (Path) unixDomainSocketAddressClass.getMethod("getPath").invoke(address); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException + | SecurityException e) { + Assertions.fail(e); + throw new AssertionError(e); + } + } else { + throw new IllegalStateException("Unsupported socket address class " + address.getClass() + + ": " + address); + } + } + + public static String separators(String path) { + StringBuilder ret = new StringBuilder(); + for (int i = 0, n = path.length(); i < n; i++) { + char c = path.charAt(i); + if ((c == '/') || (c == '\\')) { + ret.append(File.separatorChar); + } else { + ret.append(c); + } + } + return ret.toString(); + } + + @Test + public void testLargeBody() throws Exception { + String uri = "http://localhost:1234/path"; + + byte[] payload = new byte[512 * 1024]; // 512k + new Random().nextBytes(payload); + + start(new Handler.Abstract() { + @Override + public boolean handle(Request request, Response response, Callback callback) + throws Exception { + response.getHeaders().put(HttpHeader.CONTENT_TYPE, "application/octet-stream"); + + response.write(true, ByteBuffer.wrap(payload), new Callback() { + }); + + return true; + } + }); + + // ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath); + ClientConnector clientConnector = AFSocketClientConnector.withSocketAddress(AFUNIXSocketAddress + .of(unixDomainPath)); + HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector)); + httpClient.start(); + try { + ContentResponse response = httpClient.newRequest(uri).timeout(5, TimeUnit.SECONDS).send(); + + assertEquals(HttpStatus.OK_200, response.getStatus()); + byte[] data = response.getContent(); + assertArrayEquals(payload, data); + } finally { + httpClient.stop(); + } + } +} \ No newline at end of file diff --git a/junixsocket-mysql/pom.xml b/junixsocket-mysql/pom.xml index bacd7f4a3..0ebfd07ca 100644 --- a/junixsocket-mysql/pom.xml +++ b/junixsocket-mysql/pom.xml @@ -6,7 +6,7 @@ com.kohlschutter.junixsocket junixsocket - 2.6.1 + 2.10.1 ../pom.xml junixsocket-mysql @@ -20,12 +20,15 @@ com.kohlschutter.junixsocket junixsocket-common - ${project.version} - mysql - mysql-connector-java - 8.0.30 + com.kohlschutter.junixsocket + junixsocket-native-common + test + + + com.mysql + mysql-connector-j provided diff --git a/junixsocket-mysql/src/main/java/module-info.java b/junixsocket-mysql/src/main/java/module-info.java index 300b929d6..494994200 100644 --- a/junixsocket-mysql/src/main/java/module-info.java +++ b/junixsocket-mysql/src/main/java/module-info.java @@ -8,7 +8,8 @@ requires java.sql; requires java.base; - requires mysql.connector.java; + // requires mysql.connector.java; // until 8.0.30 + requires mysql.connector.j; // from 8.0.31 requires static com.kohlschutter.annotations.compiletime; requires static org.eclipse.jdt.annotation; diff --git a/junixsocket-mysql/src/main/java/org/newsclub/net/mysql/AFUNIXDatabaseSocketFactory.java b/junixsocket-mysql/src/main/java/org/newsclub/net/mysql/AFUNIXDatabaseSocketFactory.java index 4693d062b..8d173947e 100644 --- a/junixsocket-mysql/src/main/java/org/newsclub/net/mysql/AFUNIXDatabaseSocketFactory.java +++ b/junixsocket-mysql/src/main/java/org/newsclub/net/mysql/AFUNIXDatabaseSocketFactory.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,13 +31,13 @@ /** * Connect to mysql databases (and compatibles) using UNIX domain sockets. - * + * * NOTE: This SocketFactory currently implements the "old" Connector/J SocketFactory. This may * change in the future. - * + * * For the time being, see AFUNIXDatabaseSocketFactoryCJ to forcibly use the new "CJ"-style * SocketFactory. - * + * * @see AFUNIXDatabaseSocketFactoryCJ */ @SuppressWarnings("deprecation") diff --git a/junixsocket-mysql/src/main/java/org/newsclub/net/mysql/AFUNIXDatabaseSocketFactoryCJ.java b/junixsocket-mysql/src/main/java/org/newsclub/net/mysql/AFUNIXDatabaseSocketFactoryCJ.java index e378ccb41..c2888ca6e 100644 --- a/junixsocket-mysql/src/main/java/org/newsclub/net/mysql/AFUNIXDatabaseSocketFactoryCJ.java +++ b/junixsocket-mysql/src/main/java/org/newsclub/net/mysql/AFUNIXDatabaseSocketFactoryCJ.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ */ package org.newsclub.net.mysql; -import java.io.Closeable; import java.io.File; import java.io.IOException; import java.net.Socket; @@ -26,6 +25,7 @@ import org.newsclub.net.unix.AFUNIXSocketAddress; import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; +import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.conf.PropertySet; import com.mysql.cj.conf.RuntimeProperty; import com.mysql.cj.protocol.ExportControlled; @@ -38,7 +38,6 @@ */ public class AFUNIXDatabaseSocketFactoryCJ implements SocketFactory { private AFUNIXSocket rawSocket; - private Socket sslSocket; /** * Creates a new instance. @@ -46,11 +45,11 @@ public class AFUNIXDatabaseSocketFactoryCJ implements SocketFactory { public AFUNIXDatabaseSocketFactoryCJ() { } - @SuppressWarnings({"unchecked", "exports"}) + @SuppressWarnings({"unchecked"}) @SuppressFBWarnings("EI_EXPOSE_REP") @Override - public T connect(String hostname, int portNumber, PropertySet props, - int loginTimeout) throws IOException { + public Socket connect(String hostname, int portNumber, + @SuppressWarnings("exports") PropertySet props, int loginTimeout) throws IOException { // Adjust the path to your MySQL socket by setting the // "junixsocket.file" property // If no socket path is given, use the default: /tmp/mysql.sock @@ -63,18 +62,22 @@ public T connect(String hostname, int portNumber, Property } final File socketFile = new File(sock); - this.rawSocket = AFUNIXSocket.connectTo(AFUNIXSocketAddress.of(socketFile)); - this.sslSocket = rawSocket; - return (T) rawSocket; + AFUNIXSocket socket = AFUNIXSocket.newInstance(); + + int connectTimeout = props.getIntegerProperty(PropertyKey.connectTimeout).getValue(); + int timeout = MysqlHelper.shorterTimeout(connectTimeout, loginTimeout); + + socket.connect(AFUNIXSocketAddress.of(socketFile), timeout); + + return (this.rawSocket = socket); } - @SuppressWarnings({"unchecked", "exports"}) + @SuppressWarnings({"unchecked"}) @SuppressFBWarnings("EI_EXPOSE_REP") @Override - public T performTlsHandshake(SocketConnection socketConnection, - ServerSession serverSession) throws IOException { - this.sslSocket = ExportControlled.performTlsHandshake(this.rawSocket, socketConnection, + public Socket performTlsHandshake(@SuppressWarnings("exports") SocketConnection socketConnection, + @SuppressWarnings("exports") ServerSession serverSession) throws IOException { + return ExportControlled.performTlsHandshake(this.rawSocket, socketConnection, serverSession == null ? null : serverSession.getServerVersion(), null); - return (T) this.sslSocket; } } diff --git a/junixsocket-mysql/src/main/java/org/newsclub/net/mysql/MysqlHelper.java b/junixsocket-mysql/src/main/java/org/newsclub/net/mysql/MysqlHelper.java new file mode 100644 index 000000000..a02460866 --- /dev/null +++ b/junixsocket-mysql/src/main/java/org/newsclub/net/mysql/MysqlHelper.java @@ -0,0 +1,43 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.mysql; + +/** + * Code sharable between code in junixsocket-mysql. + * + * @author Christian Kohlschütter + */ +final class MysqlHelper { + private MysqlHelper() { + } + + /** + * Returns the shorter timeout of two given timeouts (assuming 0 means "unlimited"). + * + * @param a The first timeout. + * @param b The second timeout. + * @return The shorter timeout. + */ + static int shorterTimeout(int a, int b) { + if (a == 0 || (b != 0 && b < a)) { + return b; + } else { + return a; + } + } +} diff --git a/junixsocket-mysql/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-mysql/reflect-config.json b/junixsocket-mysql/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-mysql/reflect-config.json new file mode 100644 index 000000000..0f67ba412 --- /dev/null +++ b/junixsocket-mysql/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-mysql/reflect-config.json @@ -0,0 +1,16 @@ +[ +{ + "name":"com.mysql.cj.protocol.SocketFactory" +}, +{ + "name":"com.mysql.jdbc.SocketFactory" +}, +{ + "name":"org.newsclub.net.mysql.AFUNIXDatabaseSocketFactory", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.mysql.AFUNIXDatabaseSocketFactoryCJ", + "methods":[{"name":"","parameterTypes":[] }] +} +] diff --git a/junixsocket-mysql/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-mysql/resource-config.json b/junixsocket-mysql/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-mysql/resource-config.json new file mode 100644 index 000000000..7a67ef982 --- /dev/null +++ b/junixsocket-mysql/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-mysql/resource-config.json @@ -0,0 +1,7 @@ +{ + "bundles":[{ + "name":"com.mysql.cj.LocalizedErrorMessages", + "locales":["und"] + } + ] +} diff --git a/junixsocket-mysql/src/site/site.xml b/junixsocket-mysql/src/site/site.xml index 873972284..6d13afab6 100644 --- a/junixsocket-mysql/src/site/site.xml +++ b/junixsocket-mysql/src/site/site.xml @@ -7,6 +7,7 @@ org.apache.maven.skins maven-fluido-skin + 2.0.0-M8 diff --git a/junixsocket-mysql/src/test/java/org/newsclub/net/mysql/AFUNIXDatabaseSocketFactoryCJTest.java b/junixsocket-mysql/src/test/java/org/newsclub/net/mysql/AFUNIXDatabaseSocketFactoryCJTest.java new file mode 100644 index 000000000..ad555d4c3 --- /dev/null +++ b/junixsocket-mysql/src/test/java/org/newsclub/net/mysql/AFUNIXDatabaseSocketFactoryCJTest.java @@ -0,0 +1,61 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.mysql; + +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; + +import java.io.IOException; +import java.time.Duration; + +import org.junit.jupiter.api.Test; +import org.newsclub.net.unix.AFUNIXServerSocket; +import org.newsclub.net.unix.AFUNIXSocketAddress; + +import com.mysql.cj.conf.DefaultPropertySet; +import com.mysql.cj.conf.StringPropertyDefinition; + +public class AFUNIXDatabaseSocketFactoryCJTest extends MysqlSocketFactoryTestBase { + + @Test + public void testConnectTimeout() throws Exception { + AFUNIXSocketAddress addr = AFUNIXSocketAddress.ofNewTempFile(); + try (AFUNIXServerSocket serverSocket = AFUNIXServerSocket.newInstance()) { + serverSocket.bind(addr, 1); + + AFUNIXDatabaseSocketFactoryCJ sf = new AFUNIXDatabaseSocketFactoryCJ(); + + DefaultPropertySet props = new DefaultPropertySet(); + props.addProperty(new StringPropertyDefinition("junixsocket.file", "junixsocket.file", addr + .getFile().toString(), true, "description", "0", "category", 1).createRuntimeProperty()); + + assertTimeoutPreemptively(Duration.ofSeconds(5), () -> { + try { + sf.connect("localhost", 0, props, 1); + } catch (IOException ignore) { + // ignore + } + }); + } + } + + @Override + protected String socketFactory() { + return "org.newsclub.net.mysql.AFUNIXDatabaseSocketFactoryCJ"; + } + +} diff --git a/junixsocket-mysql/src/test/java/org/newsclub/net/mysql/AFUNIXDatabaseSocketFactoryTest.java b/junixsocket-mysql/src/test/java/org/newsclub/net/mysql/AFUNIXDatabaseSocketFactoryTest.java new file mode 100644 index 000000000..55ad183f6 --- /dev/null +++ b/junixsocket-mysql/src/test/java/org/newsclub/net/mysql/AFUNIXDatabaseSocketFactoryTest.java @@ -0,0 +1,54 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.mysql; + +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; + +import java.io.IOException; +import java.time.Duration; +import java.util.Properties; + +import org.junit.jupiter.api.Test; +import org.newsclub.net.unix.AFUNIXServerSocket; +import org.newsclub.net.unix.AFUNIXSocketAddress; + +public class AFUNIXDatabaseSocketFactoryTest extends MysqlSocketFactoryTestBase { + + @Test + public void testConnectTimeout() throws Exception { + AFUNIXSocketAddress addr = AFUNIXSocketAddress.ofNewTempFile(); + try (AFUNIXServerSocket serverSocket = AFUNIXServerSocket.newInstance()) { + serverSocket.bind(addr, 1); + + AFUNIXDatabaseSocketFactory sf = new AFUNIXDatabaseSocketFactory(); + + assertTimeoutPreemptively(Duration.ofSeconds(5), () -> { + try { + sf.connect("localhost", 0, new Properties()); + } catch (IOException ignore) { + // ignore + } + }); + } + } + + @Override + protected String socketFactory() { + return "org.newsclub.net.mysql.AFUNIXDatabaseSocketFactory"; + } +} diff --git a/junixsocket-mysql/src/test/java/org/newsclub/net/mysql/MysqlCredentials.java b/junixsocket-mysql/src/test/java/org/newsclub/net/mysql/MysqlCredentials.java new file mode 100644 index 000000000..ecc06d323 --- /dev/null +++ b/junixsocket-mysql/src/test/java/org/newsclub/net/mysql/MysqlCredentials.java @@ -0,0 +1,69 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.mysql; + +import com.kohlschutter.testutil.TestAbortedNotAnIssueException; + +final class MysqlCredentials { + private final String mysqlSock; + private final String user; + private final String password; + private final String database; + + private MysqlCredentials(String mysqlSock, String user, String password, String database) { + this.mysqlSock = mysqlSock; + this.user = user; + this.password = password; + this.database = database; + } + + public static MysqlCredentials getCredentials() throws TestAbortedNotAnIssueException { + String mysqlSock = System.getProperty("selftest.mysql.sock", ""); + if (mysqlSock.isEmpty()) { + throw new TestAbortedNotAnIssueException( + "Specify -Dselftest.mysql.sock=/tmp/mysql.sock or similar to enable test"); + } + String user = System.getProperty("selftest.mysql.user", "root"); + String password = System.getProperty("selftest.mysql.password", ""); + String database = System.getProperty("selftest.mysql.database", "mysql"); + + return new MysqlCredentials(mysqlSock, user, password, database); + } + + public String getMysqlSock() { + return mysqlSock; + } + + public String getUser() { + return user; + } + + public String getPassword() { + return password; + } + + public String getDatabase() { + return database; + } + + @Override + public String toString() { + return "-Dselftest.mysql.sock=" + mysqlSock + " -Dselftest.mysql.user=" + user + + " -Dselftest.mysql.password=" + password + " -Dselftest.mysql.database=" + database; + } +} diff --git a/junixsocket-mysql/src/test/java/org/newsclub/net/mysql/MysqlSocketFactoryTestBase.java b/junixsocket-mysql/src/test/java/org/newsclub/net/mysql/MysqlSocketFactoryTestBase.java new file mode 100644 index 000000000..a7e3a7cfe --- /dev/null +++ b/junixsocket-mysql/src/test/java/org/newsclub/net/mysql/MysqlSocketFactoryTestBase.java @@ -0,0 +1,80 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.mysql; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.junit.jupiter.api.Test; +import org.newsclub.net.unix.AFUNIXSocketAddress; + +import com.kohlschutter.testutil.TestAbortedWithImportantMessageException; +import com.kohlschutter.testutil.TestAbortedWithImportantMessageException.MessageType; +import com.mysql.cj.jdbc.exceptions.CommunicationsException; + +abstract class MysqlSocketFactoryTestBase { + + @Test + public void testDriverManagerConnectionToMissingServer() throws Exception { + AFUNIXSocketAddress addr = AFUNIXSocketAddress.ofNewTempFile(); + try (Connection unused = DriverManager.getConnection("jdbc:mysql://localhost/db?socketFactory=" + + socketFactory() + "&junixsocket.file=" + addr.getPath())) { + fail( + "Should have thrown an exception since we're trying to connect to a non-existing database server"); + } catch (CommunicationsException e) { + // expected + } + } + + @Test + public void testDriverManagerConnectionToMysqlSock() throws Exception { + MysqlCredentials creds = MysqlCredentials.getCredentials(); + + AFUNIXSocketAddress addr = AFUNIXSocketAddress.of(new File("/tmp/mysql.sock")); + boolean connected = false; + try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/mysql?socketFactory=" + + socketFactory() + "&junixsocket.file=" + addr.getPath(), creds.getUser(), creds + .getPassword())) { + connected = true; + + try (PreparedStatement pstmt = conn.prepareStatement("SHOW TABLES"); + ResultSet rs = pstmt.executeQuery()) { + if (rs.next()) { + assertNotNull(rs.getString(1)); + } + } + } catch (SQLException e) { + if (connected) { + throw e; + } else { + throw new TestAbortedWithImportantMessageException(MessageType.TEST_ABORTED_WITH_ISSUES, + "Could not connect to MySQL database for testing. Please correct the following definitions: " + + creds.toString(), e); + } + } + } + + protected abstract String socketFactory(); +} diff --git a/junixsocket-mysql/src/test/java/org/newsclub/net/mysql/SelftestProvider.java b/junixsocket-mysql/src/test/java/org/newsclub/net/mysql/SelftestProvider.java new file mode 100644 index 000000000..a843fef0a --- /dev/null +++ b/junixsocket-mysql/src/test/java/org/newsclub/net/mysql/SelftestProvider.java @@ -0,0 +1,72 @@ +/* + * junixsocket + * + * Copyright 2009-2024 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.newsclub.net.mysql; + +import java.io.PrintWriter; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +/** + * Provides references to all "junixsocket-rmi" tests that should be included in + * junixsocket-selftest. + * + * @author Christian Kohlschütter + */ +public class SelftestProvider { + private static final Set MISSING = new HashSet<>(); + + private static void requiresClass(String className) { + try { + Class.forName(className); + } catch (ClassNotFoundException e) { + MISSING.add(className); + } + } + + static { + requiresClass("com.mysql.jdbc.SocketFactory"); + requiresClass("com.mysql.cj.protocol.SocketFactory"); + } + + public Map[]> tests() { + Map[]> tests = new LinkedHashMap<>(); + tests.put("junixsocket-mysql", new Class[] { // + AFUNIXDatabaseSocketFactoryTest.class, AFUNIXDatabaseSocketFactoryCJTest.class,}); + + return tests; + } + + public Set modulesDisabledByDefault() { + if (MISSING.isEmpty()) { + return Collections.emptySet(); + } else { + return Collections.singleton("junixsocket-mysql"); + } + } + + public void printAdditionalProperties(PrintWriter out) { + if (MISSING.isEmpty()) { + out.println("junixsocket-mysql: All requirements are met"); + } else { + out.println("junixsocket-mysql: The following class requirements are not met: " + MISSING); + } + } +} diff --git a/junixsocket-native-android/META-INF/MANIFEST.MF b/junixsocket-native-android/META-INF/MANIFEST.MF new file mode 100644 index 000000000..4b797f9d8 --- /dev/null +++ b/junixsocket-native-android/META-INF/MANIFEST.MF @@ -0,0 +1,5 @@ +Manifest-Version: 1.0 +Created-By: Maven JAR Plugin 3.3.0 +Build-Jdk-Spec: 20 +Multi-Release: true + diff --git a/junixsocket-native-android/META-INF/maven/com.kohlschutter.junixsocket/junixsocket-native-android/git.properties b/junixsocket-native-android/META-INF/maven/com.kohlschutter.junixsocket/junixsocket-native-android/git.properties new file mode 100644 index 000000000..c7e13a5b3 --- /dev/null +++ b/junixsocket-native-android/META-INF/maven/com.kohlschutter.junixsocket/junixsocket-native-android/git.properties @@ -0,0 +1,7 @@ +#Generated by Git-Commit-Id-Plugin +git.build.version=2.7.0-SNAPSHOT +git.commit.id.abbrev=edf13c8 +git.commit.id.describe=junixsocket-2.6.2-46-gedf13c8-dirty +git.commit.id.full=edf13c8c86092b6094ad7443c5a8cee2bec9aa96 +git.commit.time=2023-06-17T17\:30\:21+02\:00 +git.dirty=true diff --git a/junixsocket-native-android/META-INF/maven/com.kohlschutter.junixsocket/junixsocket-native-android/pom.properties b/junixsocket-native-android/META-INF/maven/com.kohlschutter.junixsocket/junixsocket-native-android/pom.properties new file mode 100644 index 000000000..4d210ef64 --- /dev/null +++ b/junixsocket-native-android/META-INF/maven/com.kohlschutter.junixsocket/junixsocket-native-android/pom.properties @@ -0,0 +1,3 @@ +artifactId=junixsocket-native-android +groupId=com.kohlschutter.junixsocket +version=2.7.0-SNAPSHOT diff --git a/junixsocket-native-android/META-INF/maven/com.kohlschutter.junixsocket/junixsocket-native-android/pom.xml b/junixsocket-native-android/META-INF/maven/com.kohlschutter.junixsocket/junixsocket-native-android/pom.xml new file mode 100644 index 000000000..36a5a30e1 --- /dev/null +++ b/junixsocket-native-android/META-INF/maven/com.kohlschutter.junixsocket/junixsocket-native-android/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + junixsocket-native-android + + jar + + + com.kohlschutter.junixsocket + junixsocket + 2.9.1 + ../pom.xml + + junixsocket-native-android + + ${project.parent.basedir} + aar + + + Native junixsocket libraries for Android + + + + com.kohlschutter.junixsocket + junixsocket-common + ${project.version} + + + com.kohlschutter.junixsocket + junixsocket-native + ${project.version} + aarch64-Android-clang-llvm-jni + nar + + + + + + release + + + + org.apache.maven.plugins + maven-jar-plugin + + + build-aar + package + + jar + + + + + + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + true + + + + + diff --git a/junixsocket-native-android/pom.xml b/junixsocket-native-android/pom.xml new file mode 100644 index 000000000..312964c75 --- /dev/null +++ b/junixsocket-native-android/pom.xml @@ -0,0 +1,163 @@ + + + 4.0.0 + junixsocket-native-android + + pom + + + com.kohlschutter.junixsocket + junixsocket + 2.10.1 + ../pom.xml + + junixsocket-native-android + + ${project.parent.basedir} + ${project.name}-${project.version}.aar + + + Native junixsocket libraries for Android + + + + com.kohlschutter.junixsocket + junixsocket-common + + + com.kohlschutter.junixsocket + junixsocket-native + ${project.version} + aarch64-Android-clang-llvm-jni + nar + provided + true + + + com.kohlschutter.junixsocket + junixsocket-native + ${project.version} + x86_64-Android-clang-llvm-jni + nar + provided + true + + + com.kohlschutter.junixsocket + junixsocket-native + ${project.version} + i686-Android-clang-llvm-jni + nar + provided + true + + + com.kohlschutter.junixsocket + junixsocket-native + ${project.version} + arm-Android-clang-llvm-jni + nar + provided + true + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + multirelease-jar + none + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + unpack-nars + package + + unpack-dependencies + + + **/lib*.so + nar + ${project.build.directory} + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + build-aar + package + + single + + + + src/assembly/aar.xml + + ${aarFinalName} + false + false + + + + + + com.kohlschutter.mavenplugins + copy-rename-maven-plugin + + + rename-zip-to-aar + package + + rename + + + + ${project.build.directory}/${aarFinalName}.zip + + ${project.build.directory}/${aarFinalName} + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-aar + package + + attach-artifact + + + + + + ${project.build.directory}/${aarFinalName} + + aar + + + + + + + + + diff --git a/junixsocket-native-android/src/aar/AndroidManifest.xml b/junixsocket-native-android/src/aar/AndroidManifest.xml new file mode 100644 index 000000000..0d7dd678f --- /dev/null +++ b/junixsocket-native-android/src/aar/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/junixsocket-native-android/src/aar/R.txt b/junixsocket-native-android/src/aar/R.txt new file mode 100644 index 000000000..e69de29bb diff --git a/junixsocket-native-android/src/aar/classes.jar b/junixsocket-native-android/src/aar/classes.jar new file mode 100644 index 000000000..84e5ed105 Binary files /dev/null and b/junixsocket-native-android/src/aar/classes.jar differ diff --git a/junixsocket-native-android/src/assembly/aar.xml b/junixsocket-native-android/src/assembly/aar.xml new file mode 100644 index 000000000..1b9cc6a28 --- /dev/null +++ b/junixsocket-native-android/src/assembly/aar.xml @@ -0,0 +1,51 @@ + + aar + + + zip + + + false + false + + + + ${project.basedir}/src/aar + + + * + + + + res + + **/* + + + + + + + ${project.build.directory}/lib/aarch64-Android-clang/jni/libjunixsocket-native-${project.version}.so + jni/arm64-v8a + libjunixsocket-native.so + + + ${project.build.directory}/lib/x86_64-Android-clang/jni/libjunixsocket-native-${project.version}.so + jni/x86_64 + libjunixsocket-native.so + + + ${project.build.directory}/lib/i686-Android-clang/jni/libjunixsocket-native-${project.version}.so + jni/x86 + libjunixsocket-native.so + + + ${project.build.directory}/lib/arm-Android-clang/jni/libjunixsocket-native-${project.version}.so + jni/armeabi-v7a + libjunixsocket-native.so + + + diff --git a/junixsocket-native-common/pom.xml b/junixsocket-native-common/pom.xml index 49bf4aaaf..343f19382 100644 --- a/junixsocket-native-common/pom.xml +++ b/junixsocket-native-common/pom.xml @@ -7,7 +7,7 @@ com.kohlschutter.junixsocket junixsocket - 2.6.1 + 2.10.1 ../pom.xml junixsocket-native-common @@ -103,8 +103,7 @@ release - cross - true + release @@ -164,6 +163,17 @@ runtime true + - 2.6.1 + 2.10.1 ../pom.xml junixsocket-native-cross @@ -31,6 +31,11 @@ release + + + release + + false true @@ -106,6 +111,7 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} @@ -132,6 +138,7 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} @@ -165,6 +172,7 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} @@ -198,6 +206,7 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} @@ -231,6 +240,7 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} @@ -264,6 +274,7 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} @@ -297,6 +308,7 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} @@ -330,6 +342,7 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} @@ -363,6 +376,143 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} + + + + + crosscompile-aarch64-Android-clang + + run + + + ${junixsocket.cross.disabled} + true + true + + clean + install + + ${project.basedir}/../ + + junixsocket-native + + + llvm + + + aarch64-Android-clang + true + ${project.build.directory}/junixsocket-native-aarch64-Android-clang-llvm + aarch64-linux-android30 + so + aarch64-Android-clang-llvm-jni + ${ignorant} + ${gpgkeyname} + ${gpg.executable} + ${retrolambda} + + + + + crosscompile-x86_64-Android-clang + + run + + + ${junixsocket.cross.disabled} + true + true + + clean + install + + ${project.basedir}/../ + + junixsocket-native + + + llvm + + + x86_64-Android-clang + true + ${project.build.directory}/junixsocket-native-x86_64-Android-clang-llvm + x86_64-linux-android30 + so + x86_64-Android-clang-llvm-jni + ${ignorant} + ${gpgkeyname} + ${gpg.executable} + ${retrolambda} + + + + + crosscompile-i686-Android-clang + + run + + + ${junixsocket.cross.disabled} + true + true + + clean + install + + ${project.basedir}/../ + + junixsocket-native + + + llvm + + + i686-Android-clang + true + ${project.build.directory}/junixsocket-native-i686-Android-clang-llvm + i686-linux-android30 + so + i686-Android-clang-llvm-jni + ${ignorant} + ${gpgkeyname} + ${gpg.executable} + ${retrolambda} + + + + + crosscompile-arm-Android-clang + + run + + + ${junixsocket.cross.disabled} + true + true + + clean + install + + ${project.basedir}/../ + + junixsocket-native + + + llvm + + + arm-Android-clang + true + ${project.build.directory}/junixsocket-native-arm-Android-clang-llvm + arm-linux-androideabi30 + so + arm-Android-clang-llvm-jni + ${ignorant} + ${gpgkeyname} + ${gpg.executable} + ${retrolambda} @@ -397,6 +547,7 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} @@ -431,6 +582,7 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} @@ -464,6 +616,7 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} @@ -497,6 +650,7 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} @@ -530,6 +684,7 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} @@ -563,6 +718,7 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} @@ -597,6 +753,7 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} @@ -630,6 +787,41 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} + + + + + crosscompile-loongarch64-Linux-clang + + run + + + ${junixsocket.cross.disabled} + true + true + + clean + install + + ${project.basedir}/../ + + junixsocket-native + + + llvm + + + loongarch64-Linux-clang + true + ${project.build.directory}/junixsocket-native-loongarch64-Linux-clang-llvm + loongarch64-openEuler-linux + so + loongarch64-Linux-clang-llvm-jni + ${ignorant} + ${gpgkeyname} + ${gpg.executable} + ${retrolambda} @@ -662,6 +854,7 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} @@ -694,6 +887,7 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} @@ -726,6 +920,7 @@ ${ignorant} ${gpgkeyname} ${gpg.executable} + ${retrolambda} @@ -847,6 +1042,70 @@ false + + install-aarch64-Android-clang + ${junixsocket.cross.install-phase} + + install-file + + + ${project.build.directory}/junixsocket-native-aarch64-Android-clang-llvm/junixsocket-native-${project.version}-aarch64-Android-clang-jni.nar + ${project.groupId} + junixsocket-native + ${project.version} + nar + aarch64-Android-clang-llvm-jni + false + + + + install-x86_64-Android-clang + ${junixsocket.cross.install-phase} + + install-file + + + ${project.build.directory}/junixsocket-native-x86_64-Android-clang-llvm/junixsocket-native-${project.version}-x86_64-Android-clang-jni.nar + ${project.groupId} + junixsocket-native + ${project.version} + nar + x86_64-Android-clang-llvm-jni + false + + + + install-i686-Android-clang + ${junixsocket.cross.install-phase} + + install-file + + + ${project.build.directory}/junixsocket-native-i686-Android-clang-llvm/junixsocket-native-${project.version}-i686-Android-clang-jni.nar + ${project.groupId} + junixsocket-native + ${project.version} + nar + i686-Android-clang-llvm-jni + false + + + + install-arm-Android-clang + ${junixsocket.cross.install-phase} + + install-file + + + ${project.build.directory}/junixsocket-native-arm-Android-clang-llvm/junixsocket-native-${project.version}-arm-Android-clang-jni.nar + ${project.groupId} + junixsocket-native + ${project.version} + nar + arm-Android-clang-llvm-jni + false + + - 2.6.1 + 2.10.1 ../pom.xml junixsocket-native-custom diff --git a/junixsocket-native-custom/src/main/java/org/newsclub/lib/junixsocket/custom/NarMetadata.java b/junixsocket-native-custom/src/main/java/org/newsclub/lib/junixsocket/custom/NarMetadata.java index 4613c7e5a..f05bd26ba 100644 --- a/junixsocket-native-custom/src/main/java/org/newsclub/lib/junixsocket/custom/NarMetadata.java +++ b/junixsocket-native-custom/src/main/java/org/newsclub/lib/junixsocket/custom/NarMetadata.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,11 +21,11 @@ /** * Marker class to find native libraries in the classpath. - * + * * @author Christian Kohlschütter */ public final class NarMetadata { - @ExcludeFromCodeCoverageGeneratedReport + @ExcludeFromCodeCoverageGeneratedReport(reason = "unreachable") private NarMetadata() { throw new IllegalStateException("No instances"); } diff --git a/junixsocket-native-custom/src/main/java/org/newsclub/lib/junixsocket/custom/package-info.java b/junixsocket-native-custom/src/main/java/org/newsclub/lib/junixsocket/custom/package-info.java index 068069722..6d0528155 100644 --- a/junixsocket-native-custom/src/main/java/org/newsclub/lib/junixsocket/custom/package-info.java +++ b/junixsocket-native-custom/src/main/java/org/newsclub/lib/junixsocket/custom/package-info.java @@ -1,11 +1,11 @@ /** * Helper package to identify the Maven artifact with JNI libraries for specific architectures. - * + * * There are multiple artifacts with the same identifier (junixsocket-native), one per architecture. - * + * * If you want to run junixsocket on your architecture, you need to make sure that the correct one * is in the classpath. - * + * * See "junixsocket-native-common" for an artifact containing the commonly used architectures. */ package org.newsclub.lib.junixsocket.custom; diff --git a/junixsocket-native-custom/src/main/java7/multirelease.md b/junixsocket-native-custom/src/main/java7/multirelease.md new file mode 100644 index 000000000..713e9e182 --- /dev/null +++ b/junixsocket-native-custom/src/main/java7/multirelease.md @@ -0,0 +1,3 @@ +This repository is kept compatible with Java 7. + +Do not remove this file, it is used as a marker by the Maven build scripts. diff --git a/junixsocket-native-custom/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-custom/reflect-config.json b/junixsocket-native-custom/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-custom/reflect-config.json new file mode 100644 index 000000000..3ca368701 --- /dev/null +++ b/junixsocket-native-custom/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-custom/reflect-config.json @@ -0,0 +1,5 @@ +[ +{ + "name":"org.newsclub.lib.junixsocket.custom.NarMetadata" +} +] diff --git a/junixsocket-native-custom/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-custom/resource-config.json b/junixsocket-native-custom/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-custom/resource-config.json new file mode 100644 index 000000000..6ea5af423 --- /dev/null +++ b/junixsocket-native-custom/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-custom/resource-config.json @@ -0,0 +1,9 @@ +{ + "resources":{ + "includes":[ + { + "pattern":"\\QMETA-INF/maven/com.kohlschutter.junixsocket/junixsocket-native-custom/pom.properties\\E" + } + ]}, + "bundles":[] +} \ No newline at end of file diff --git a/junixsocket-native-custom/src/site/site.xml b/junixsocket-native-custom/src/site/site.xml index 873972284..6d13afab6 100644 --- a/junixsocket-native-custom/src/site/site.xml +++ b/junixsocket-native-custom/src/site/site.xml @@ -7,6 +7,7 @@ org.apache.maven.skins maven-fluido-skin + 2.0.0-M8 diff --git a/junixsocket-native-graalvm/bin/build-selftest b/junixsocket-native-graalvm/bin/build-selftest index 396095776..f15f6959d 100755 --- a/junixsocket-native-graalvm/bin/build-selftest +++ b/junixsocket-native-graalvm/bin/build-selftest @@ -7,6 +7,9 @@ # SPDX-License-Identifier: Apache-2.0 # cd "$(dirname $0)/../" +nativeGraalVmDir="$(pwd)" + +[[ -n "$GRAALVM_HOME" ]] && export PATH="$GRAALVM_HOME"/bin:$PATH java -version 2>&1 | grep -q GraalVM if [[ $? -ne 0 ]]; then @@ -72,9 +75,37 @@ fi jar=$(cd $(dirname "$jar"); pwd)/$(basename "$jar") echo jar: $jar +echo +echo Checking availability of additional dependencies required for coverage + +mysqlDepVersion=$(grep -A2 mysql-connector "${nativeGraalVmDir}/../pom.xml" | grep '' | grep '' | tr -d ' ' | sed -E 's|||g') +if [[ -n "$mysqlDepVersion" ]]; then + echo "Detected version for mysql-connector-j dependency: $mysqlDepVersion" +else + mysqlDepVersion=8.3.0 + echo "[WARNING] could not detect version for mysql-connector-j dependency, using ${mysqlDepVersion}" +fi + +mysqlDep="$HOME/.m2/repository/com/mysql/mysql-connector-j/8.3.0/mysql-connector-j-8.3.0.jar" +if [[ -f "$mysqlDep" ]]; then + echo "Using mysql-connector-j dependency from: $mysqlDep" +else + echo "[WARNING] mysql-connector-j dependency is missing: $mysqlDep" >&2 +fi + +selftestArgs=( + -Dselftest.enable-module.junixsocket-common.JavaInet=true + -Dselftest.enable-module.junixsocket-common.JEP380=true + -cp "$jar":"$mysqlDep" +) + echo echo Running junixsocket-selftest with GraalVM native-image-agent... -( set -x ; java -agentlib:native-image-agent=config-output-dir=${tmpDir}/native-image.{pid} -Dselftest.skip.junixsocket-rmi=force -jar "$jar" >/dev/null ) +( + set -x + java -agentlib:native-image-agent=config-output-dir=${tmpDir}/native-image.{pid} \ + ${selftestArgs[@]} org.newsclub.net.unix.selftest.Selftest +) if [[ $? -ne 0 ]]; then echo "Error: junixsocket-selftest failed" >&2 @@ -91,13 +122,18 @@ cd bin echo echo Running native-image... -( set -x ; native-image -cp "$tmpDir" --initialize-at-build-time=sun.rmi.transport.GC --report-unsupported-elements-at-runtime --no-fallback -jar "$jar" ) +( + set -x + native-image -cp "$tmpDir" --initialize-at-build-time=sun.rmi.transport.GC --report-unsupported-elements-at-runtime --no-fallback \ + ${selftestArgs[@]} org.newsclub.net.unix.selftest.Selftest +) if [[ $? -ne 0 ]]; then echo "Error: Failed to run native-image" >&2 exit 1 fi -nativeBinary="$(pwd)/${$(basename "$jar")%%.jar}" +#nativeBinary="$(pwd)/${$(basename "$jar")%%.jar}" +nativeBinary="$(pwd)/org.newsclub.net.unix.selftest.selftest" if [[ ! -e "$nativeBinary" ]]; then echo "Error: Native binary expected but not found at: $nativeBinary" >&2 exit 1 diff --git a/junixsocket-native-graalvm/bin/graalvm b/junixsocket-native-graalvm/bin/graalvm deleted file mode 100755 index d06e042bd..000000000 --- a/junixsocket-native-graalvm/bin/graalvm +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -java -version 2>&1 | grep -q GraalVM -if [[ $? -eq 0 ]]; then - exec $@ -fi - -# FIXME auto-detect -export JAVA_HOME=/Library/Java/JavaVirtualMachines/graalvm-ce-java17-22.2.0/Contents/Home -export PATH=$JAVA_HOME/bin:$PATH -exec $@ diff --git a/junixsocket-native-graalvm/bin/with-graalvm b/junixsocket-native-graalvm/bin/with-graalvm new file mode 100755 index 000000000..4f5389a26 --- /dev/null +++ b/junixsocket-native-graalvm/bin/with-graalvm @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# +# junixsocket +# Copyright 2009-2022 Christian Kohlschütter +# SPDX-License-Identifier: Apache-2.0 +# +# Script to detect and enable GraalVM. +# +# When run without arguments, an eval-able string like "GRAALVM_HOME=/path/to/graalvm" is emitted. +# Otherwise, the arguments are executed with the determined GraalVM in path (GRAALVM_HOME, JAVA_HOME and PATH set accordingly). +# +java -version 2>&1 | grep -q GraalVM +if [[ $? -eq 0 ]]; then + exec $@ +fi + +if [[ -z "$GRAALVM_HOME" ]]; then + for d in $(find /Library/Java/JavaVirtualMachines /usr/lib/jvm -maxdepth 1 -type d -name "*graalvm*" 2>/dev/null | sort -r); do + if [[ -e "$d/bin/java" ]]; then + export GRAALVM_HOME="$d" + break + elif [[ -e "$d/Contents/Home/bin/java" ]]; then + export GRAALVM_HOME="$d/Contents/Home" + break + fi + done +fi + +if [[ -z "$GRAALVM_HOME" ]]; then + echo "Error: Could not determine GRAALVM_HOME -- Please set manually" >&2 + exit 1 +else + if [[ $# -eq 0 ]]; then + echo "GRAALVM_HOME=$GRAALVM_HOME" + fi +fi + +export JAVA_HOME="$GRAALVM_HOME" +export PATH=$JAVA_HOME/bin:$PATH +exec -- $@ diff --git a/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/jni-config.json b/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/jni-config.json index 50078f6a3..2a7635ebb 100644 --- a/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/jni-config.json +++ b/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/jni-config.json @@ -8,6 +8,10 @@ "name":"java.lang.Boolean", "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }] }, +{ + "name":"java.lang.Class", + "methods":[{"name":"getName","parameterTypes":[] }] +}, { "name":"java.lang.IllegalStateException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] @@ -18,15 +22,16 @@ }, { "name":"java.lang.Integer", - "methods":[ - {"name":"","parameterTypes":["int"] }, - {"name":"intValue","parameterTypes":[] } - ] + "methods":[{"name":"","parameterTypes":["int"] }, {"name":"intValue","parameterTypes":[] }] }, { "name":"java.lang.NullPointerException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] }, +{ + "name":"java.lang.Object", + "methods":[{"name":"getClass","parameterTypes":[] }] +}, { "name":"java.lang.ProcessBuilder$RedirectPipeImpl", "fields":[{"name":"fd"}], @@ -34,24 +39,23 @@ }, { "name":"java.lang.String", - "methods":[ - {"name":"lastIndexOf","parameterTypes":["int"] }, - {"name":"substring","parameterTypes":["int"] } - ] + "methods":[{"name":"lastIndexOf","parameterTypes":["int"] }, {"name":"substring","parameterTypes":["int"] }] }, { "name":"java.lang.System", - "methods":[ - {"name":"getProperty","parameterTypes":["java.lang.String"] }, - {"name":"setProperty","parameterTypes":["java.lang.String","java.lang.String"] } - ] + "methods":[{"name":"getProperty","parameterTypes":["java.lang.String"] }, {"name":"setProperty","parameterTypes":["java.lang.String","java.lang.String"] }] +}, +{ + "name":"java.lang.ThreadLocal", + "methods":[{"name":"get","parameterTypes":[] }] }, { "name":"java.net.DatagramSocket" }, { "name":"java.net.InetSocketAddress", - "fields":[{"name":"holder"}] + "fields":[{"name":"holder"}], + "methods":[{"name":"","parameterTypes":["int"] }] }, { "name":"java.net.InetSocketAddress$InetSocketAddressHolder", @@ -85,15 +89,11 @@ "methods":[{"name":"removeKey","parameterTypes":["java.nio.channels.SelectionKey"] }] }, { - "name":"org.newsclub.net.unix.AFSelector$PollFd", - "fields":[ - {"name":"fds"}, - {"name":"ops"}, - {"name":"rops"} - ] + "name":"org.newsclub.net.unix.AFGenericServerSocket" }, { - "name":"org.newsclub.net.unix.AFTIPCSocketAddress" + "name":"org.newsclub.net.unix.AFSelector$PollFd", + "fields":[{"name":"fds"}, {"name":"ops"}, {"name":"rops"}] }, { "name":"org.newsclub.net.unix.AFUNIXDatagramSocket" @@ -109,11 +109,7 @@ }, { "name":"org.newsclub.net.unix.AFUNIXSocketCredentials", - "fields":[ - {"name":"gids"}, - {"name":"pid"}, - {"name":"uid"} - ], + "fields":[{"name":"gids"}, {"name":"pid"}, {"name":"uid"}], "methods":[{"name":"setUUID","parameterTypes":["java.lang.String"] }] }, { @@ -122,46 +118,67 @@ }, { "name":"org.newsclub.net.unix.AncillaryDataSupport", - "fields":[ - {"name":"ancillaryReceiveBuffer"}, - {"name":"pendingFileDescriptors"} - ], - "methods":[ - {"name":"receiveFileDescriptors","parameterTypes":["int[]"] }, - {"name":"setTipcDestName","parameterTypes":["int","int","int"] }, - {"name":"setTipcErrorInfo","parameterTypes":["int","int"] } - ] + "fields":[{"name":"ancillaryReceiveBuffer"}, {"name":"pendingFileDescriptors"}], + "methods":[{"name":"receiveFileDescriptors","parameterTypes":["int[]"] }, {"name":"setTipcDestName","parameterTypes":["int","int","int"] }, {"name":"setTipcErrorInfo","parameterTypes":["int","int"] }] +}, +{ + "name":"org.newsclub.net.unix.BrokenPipeSocketException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.newsclub.net.unix.ConnectionResetSocketException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] }, { "name":"org.newsclub.net.unix.InvalidArgumentSocketException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] }, +{ + "name":"org.newsclub.net.unix.NoSuchDeviceSocketException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.newsclub.net.unix.NotConnectedSocketException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, { "name":"org.newsclub.net.unix.OperationNotSupportedSocketException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] }, +{ + "name":"org.newsclub.net.unix.SocketClosedException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, { "name":"org.newsclub.net.unix.StdinSocketApp", "methods":[{"name":"main","parameterTypes":["java.lang.String[]"] }] }, +{ + "name":"org.newsclub.net.unix.darwin.system.AFSYSTEMDatagramSocket" +}, +{ + "name":"org.newsclub.net.unix.darwin.system.AFSYSTEMSocket" +}, { "name":"org.newsclub.net.unix.selftest.Selftest", "methods":[{"name":"main","parameterTypes":["java.lang.String[]"] }] }, { "name":"org.newsclub.net.unix.tipc.AFTIPCGroupRequest", - "methods":[ - {"name":"fromNative","parameterTypes":["int","int","int","int"] }, - {"name":"getFlagsValue","parameterTypes":[] }, - {"name":"getInstance","parameterTypes":[] }, - {"name":"getScopeId","parameterTypes":[] }, - {"name":"getType","parameterTypes":[] } - ] + "methods":[{"name":"fromNative","parameterTypes":["int","int","int","int"] }, {"name":"getFlagsValue","parameterTypes":[] }, {"name":"getInstance","parameterTypes":[] }, {"name":"getScopeId","parameterTypes":[] }, {"name":"getType","parameterTypes":[] }] }, { "name":"org.newsclub.net.unix.vsock.AFVSOCKDatagramSocket" }, { "name":"org.newsclub.net.unix.vsock.AFVSOCKSocket" +}, +{ + "name":"sun.rmi.transport.tcp.TCPTransport", + "fields":[{"name":"threadConnectionHandler"}] +}, +{ + "name":"sun.rmi.transport.tcp.TCPTransport$ConnectionHandler", + "fields":[{"name":"socket"}] } ] \ No newline at end of file diff --git a/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/proxy-config.json b/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/proxy-config.json index 32960f8ce..4941ba893 100644 --- a/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/proxy-config.json +++ b/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/proxy-config.json @@ -1,2 +1,17 @@ [ + { + "interfaces":["org.newsclub.net.unix.rmi.AFRMIService"] + }, + { + "interfaces":["org.newsclub.net.unix.rmi.Hello"] + }, + { + "interfaces":["org.newsclub.net.unix.rmi.NaiveFileInputStreamRemote"] + }, + { + "interfaces":["org.newsclub.net.unix.rmi.RemoteCloseable"] + }, + { + "interfaces":["org.newsclub.net.unix.rmi.TestService"] + } ] \ No newline at end of file diff --git a/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/reflect-config.json b/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/reflect-config.json index a0f66c8c9..66799488d 100644 --- a/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/reflect-config.json +++ b/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/reflect-config.json @@ -1,4 +1,50 @@ [ +{ + "name":"[B" +}, +{ + "name":"[J" +}, +{ + "name":"[Ljava.lang.StackTraceElement;" +}, +{ + "name":"[Ljava.lang.String;" +}, +{ + "name":"[Ljava.rmi.server.ObjID;" +}, +{ + "name":"[Lsun.security.pkcs.SignerInfo;" +}, +{ + "name":"apple.security.AppleProvider", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.kohlschutter.testutil.AvailabilityExecutionCondition", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.kohlschutter.testutil.AvailabilityRequirement", + "queryAllPublicMethods":true +}, +{ + "name":"com.kohlschutter.testutil.ExecutionEnvironmentExecutionCondition", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.kohlschutter.testutil.ExecutionEnvironmentRequirement", + "queryAllPublicMethods":true +}, +{ + "name":"com.kohlschutter.testutil.ForkedVMExecutionCondition", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.kohlschutter.testutil.ForkedVMRequirement", + "queryAllPublicMethods":true +}, { "name":"com.kohlschutter.testutil.ProcessUtilCondition", "methods":[{"name":"","parameterTypes":[] }] @@ -7,19 +53,274 @@ "name":"com.kohlschutter.testutil.ProcessUtilRequirement", "queryAllPublicMethods":true }, +{ + "name":"com.kohlschutter.testutil.SystemPropertyExecutionCondition", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.kohlschutter.testutil.SystemPropertyRequirement", + "queryAllPublicMethods":true +}, +{ + "name":"com.mysql.cj.conf.url.SingleConnectionUrl", + "methods":[{"name":"","parameterTypes":["com.mysql.cj.conf.ConnectionUrlParser","java.util.Properties"] }] +}, +{ + "name":"com.mysql.cj.exceptions.CJCommunicationsException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.mysql.cj.exceptions.WrongArgumentException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.mysql.cj.jdbc.AbandonedConnectionCleanupThread" +}, +{ + "name":"com.mysql.cj.jdbc.Driver" +}, +{ + "name":"com.mysql.cj.log.StandardLogger", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.mysql.cj.protocol.SocketFactory" +}, +{ + "name":"com.mysql.jdbc.SocketFactory" +}, +{ + "name":"com.sun.crypto.provider.AESCipher$General", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.AESKeyGenerator", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.ARCFOURCipher", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DESCipher", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DESedeCipher", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DHParameters", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.GaloisCounterMode$AESGCM", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.HmacCore$HmacSHA256", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.HmacCore$HmacSHA384", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.HmacPKCS12PBECore$HmacPKCS12PBE_SHA256", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.PBEKeyFactory$PBEWithMD5AndDES", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.PBES2Core$HmacSHA256AndAES_256", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.PBES2Parameters$General", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.TlsMasterSecretGenerator", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.wolfssl.provider.jce.WolfCryptProvider" +}, +{ + "name":"com.wolfssl.provider.jsse.WolfSSLProvider" +}, +{ + "name":"iaik.security.jsse.provider.IAIKJSSEProvider" +}, +{ + "name":"iaik.security.provider.IAIK" +}, +{ + "name":"java.io.IOException" +}, +{ + "name":"java.lang.Exception" +}, +{ + "name":"java.lang.Number" +}, { "name":"java.lang.ProcessBuilder$RedirectPipeImpl" }, +{ + "name":"java.lang.String" +}, +{ + "name":"java.lang.Thread", + "fields":[{"name":"threadLocalRandomProbe"}] +}, +{ + "name":"java.lang.Throwable" +}, +{ + "name":"java.lang.reflect.Proxy" +}, +{ + "name":"java.net.InetSocketAddress" +}, +{ + "name":"java.net.UnixDomainSocketAddress", + "methods":[{"name":"of","parameterTypes":["java.nio.file.Path"] }] +}, +{ + "name":"java.nio.channels.DatagramChannel", + "methods":[{"name":"open","parameterTypes":["java.net.ProtocolFamily"] }] +}, +{ + "name":"java.nio.channels.ServerSocketChannel", + "methods":[{"name":"open","parameterTypes":["java.net.ProtocolFamily"] }] +}, +{ + "name":"java.nio.channels.SocketChannel", + "methods":[{"name":"open","parameterTypes":["java.net.ProtocolFamily"] }] +}, { "name":"java.nio.channels.spi.AbstractSelectableChannel" }, +{ + "name":"java.rmi.NoSuchObjectException" +}, +{ + "name":"java.rmi.Remote", + "queryAllPublicMethods":true +}, +{ + "name":"java.rmi.RemoteException" +}, +{ + "name":"java.rmi.dgc.Lease" +}, +{ + "name":"java.rmi.dgc.VMID" +}, +{ + "name":"java.rmi.registry.Registry", + "queryAllPublicMethods":true +}, +{ + "name":"java.rmi.server.ObjID" +}, +{ + "name":"java.rmi.server.RemoteObject" +}, +{ + "name":"java.rmi.server.RemoteObjectInvocationHandler" +}, +{ + "name":"java.rmi.server.UID" +}, +{ + "name":"java.security.AlgorithmParametersSpi" +}, +{ + "name":"java.security.KeyStoreSpi" +}, { "name":"java.security.SecureRandomParameters" }, +{ + "name":"java.security.cert.CertStoreParameters" +}, +{ + "name":"java.security.interfaces.RSAPrivateKey" +}, +{ + "name":"java.security.interfaces.RSAPublicKey" +}, +{ + "name":"java.sql.SQLException", + "fields":[{"name":"next"}] +}, +{ + "name":"java.util.Collections$EmptyList" +}, +{ + "name":"java.util.Date" +}, +{ + "name":"java.util.UUID" +}, +{ + "name":"java.util.concurrent.ForkJoinTask", + "fields":[{"name":"aux"}, {"name":"status"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicBoolean", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicInteger" +}, +{ + "name":"java.util.concurrent.atomic.AtomicReference", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.Striped64", + "fields":[{"name":"base"}, {"name":"cellsBusy"}] +}, +{ + "name":"java.util.function.Consumer", + "queryAllPublicMethods":true +}, +{ + "name":"javax.security.auth.x500.X500Principal", + "fields":[{"name":"thisX500Name"}], + "methods":[{"name":"","parameterTypes":["sun.security.x509.X500Name"] }] +}, +{ + "name":"jdk.internal.misc.Unsafe" +}, { "name":"org.apiguardian.api.API", "queryAllPublicMethods":true }, +{ + "name":"org.bouncycastle.jce.provider.BouncyCastleProvider" +}, +{ + "name":"org.bouncycastle.jsse.provider.BouncyCastleJsseProvider" +}, +{ + "name":"org.bouncycastle.tls.TlsException" +}, +{ + "name":"org.conscrypt.Conscrypt" +}, +{ + "name":"org.junit.internal.AssumptionViolatedException" +}, { "name":"org.junit.jupiter.api.MethodOrderer$MethodName", "methods":[{"name":"","parameterTypes":[] }] @@ -28,10 +329,18 @@ "name":"org.junit.jupiter.api.Test", "queryAllPublicMethods":true }, +{ + "name":"org.junit.jupiter.api.TestInstance", + "queryAllPublicMethods":true +}, { "name":"org.junit.jupiter.api.TestMethodOrder", "queryAllPublicMethods":true }, +{ + "name":"org.junit.jupiter.api.TestTemplate", + "queryAllPublicMethods":true +}, { "name":"org.junit.jupiter.api.Timeout", "queryAllPublicMethods":true @@ -45,14 +354,107 @@ "methods":[{"name":"","parameterTypes":[] }] }, { - "name":"org.junit.platform.commons.annotation.Testable", - "queryAllPublicMethods":true + "name":"org.junit.jupiter.params.ParameterizedTest", + "queryAllPublicMethods":true +}, +{ + "name":"org.junit.jupiter.params.ParameterizedTestExtension", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.junit.jupiter.params.provider.AnnotationBasedArgumentsProvider", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true +}, +{ + "name":"org.junit.jupiter.params.provider.ArgumentsProvider", + "queryAllPublicMethods":true +}, +{ + "name":"org.junit.jupiter.params.provider.ArgumentsSource", + "queryAllPublicMethods":true +}, +{ + "name":"org.junit.jupiter.params.provider.EnumArgumentsProvider", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.junit.jupiter.params.provider.EnumSource", + "queryAllPublicMethods":true +}, +{ + "name":"org.junit.jupiter.params.provider.MethodArgumentsProvider", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.junit.jupiter.params.provider.MethodSource", + "queryAllPublicMethods":true +}, +{ + "name":"org.junit.jupiter.params.support.AnnotationConsumer", + "queryAllPublicMethods":true +}, +{ + "name":"org.junit.platform.commons.annotation.Testable", + "queryAllPublicMethods":true +}, +{ + "name":"org.junit.platform.launcher.listeners.UniqueIdTrackingListener" +}, +{ + "name":"org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener" +}, +{ + "name":"org.newsclub.lib.junixsocket.common.NarMetadata" +}, +{ + "name":"org.newsclub.lib.junixsocket.custom.NarMetadata" +}, +{ + "name":"org.newsclub.net.mysql.AFUNIXDatabaseSocketFactory", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.mysql.AFUNIXDatabaseSocketFactoryCJ", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.mysql.AFUNIXDatabaseSocketFactoryCJTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"testConnectTimeout","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.mysql.AFUNIXDatabaseSocketFactoryTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"testConnectTimeout","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.mysql.MysqlSocketFactoryTestBase", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "methods":[{"name":"testDriverManagerConnectionToMissingServer","parameterTypes":[] }, {"name":"testDriverManagerConnectionToMysqlSock","parameterTypes":[] }] }, { - "name":"org.newsclub.lib.junixsocket.common.NarMetadata" + "name":"org.newsclub.net.unix.AFGenericSocketAddress", + "methods":[{"name":"addressFamily","parameterTypes":[] }] }, { - "name":"org.newsclub.lib.junixsocket.custom.NarMetadata" + "name":"org.newsclub.net.unix.AFSYSTEMSocketAddress", + "methods":[{"name":"addressFamily","parameterTypes":[] }] }, { "name":"org.newsclub.net.unix.AFSocketCapabilityCondition", @@ -73,16 +475,7 @@ "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, "queryAllDeclaredConstructors":true, - "methods":[ - {"name":"","parameterTypes":[] }, - {"name":"testGeneric","parameterTypes":[] }, - {"name":"testParseFail","parameterTypes":[] }, - {"name":"testSchemesAvailable","parameterTypes":[] }, - {"name":"testServiceRangeURI","parameterTypes":[] }, - {"name":"testServiceURI","parameterTypes":[] }, - {"name":"testSocatString","parameterTypes":[] }, - {"name":"testSocketURI","parameterTypes":[] } - ] + "methods":[{"name":"","parameterTypes":[] }, {"name":"testGeneric","parameterTypes":[] }, {"name":"testParseFail","parameterTypes":[] }, {"name":"testSchemesAvailable","parameterTypes":[] }, {"name":"testServiceRangeURI","parameterTypes":[] }, {"name":"testServiceURI","parameterTypes":[] }, {"name":"testSocatString","parameterTypes":[] }, {"name":"testSocketURI","parameterTypes":[] }] }, { "name":"org.newsclub.net.unix.AFUNIXSocketAddress", @@ -95,18 +488,22 @@ "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, "queryAllDeclaredConstructors":true, - "methods":[ - {"name":"","parameterTypes":[] }, - {"name":"testAbstractNamespace","parameterTypes":[] }, - {"name":"testFileScheme","parameterTypes":[] }, - {"name":"testHttpUnix","parameterTypes":[] }, - {"name":"testParseURIandBack","parameterTypes":[] }, - {"name":"testSchemesAvailable","parameterTypes":[] }, - {"name":"testSocatString","parameterTypes":[] }, - {"name":"testURITemplate","parameterTypes":[] }, - {"name":"testURITemplateWithPortNumber","parameterTypes":[] }, - {"name":"testUnixScheme","parameterTypes":[] } - ] + "methods":[{"name":"","parameterTypes":[] }, {"name":"testAbstractNamespace","parameterTypes":[] }, {"name":"testCraftDeserialization","parameterTypes":[] }, {"name":"testFileScheme","parameterTypes":[] }, {"name":"testHttpUnix","parameterTypes":[] }, {"name":"testParseURIandBack","parameterTypes":[] }, {"name":"testSchemesAvailable","parameterTypes":[] }, {"name":"testSerialize","parameterTypes":[] }, {"name":"testSocatString","parameterTypes":[] }, {"name":"testURITemplate","parameterTypes":[] }, {"name":"testURITemplateWithPortNumber","parameterTypes":[] }, {"name":"testUnixScheme","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.AFUNIXSocketCredentials" +}, +{ + "name":"org.newsclub.net.unix.AFUNIXSocketFactory$FactoryArg", + "methods":[{"name":"","parameterTypes":["java.io.File"] }, {"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.newsclub.net.unix.AFUNIXSocketFactory$SystemProperty", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.AFUNIXSocketFactory$URIScheme", + "methods":[{"name":"","parameterTypes":[] }] }, { "name":"org.newsclub.net.unix.AFVSOCKSocketAddress", @@ -118,11 +515,10 @@ "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "methods":[ - {"name":"testAcceptWithoutBindToService","parameterTypes":[] }, - {"name":"testCatchTimeout","parameterTypes":[] }, - {"name":"testTimeoutAfterDelay","parameterTypes":[] } - ] + "methods":[{"name":"testAcceptWithoutBindToService","parameterTypes":[] }, {"name":"testCatchTimeout","parameterTypes":[] }, {"name":"testPendingAcceptCloseServerSocketDelayed","parameterTypes":[] }, {"name":"testPendingAcceptCloseServerSocketImmediately","parameterTypes":[] }, {"name":"testTimeoutAfterDelay","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.AddressUnavailableSocketException" }, { "name":"org.newsclub.net.unix.AncillaryMessageTest", @@ -137,10 +533,10 @@ "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "methods":[ - {"name":"testAvailableAtClient","parameterTypes":[] }, - {"name":"testAvailableAtServer","parameterTypes":[] } - ] + "methods":[{"name":"testAvailableAtClient","parameterTypes":[] }, {"name":"testAvailableAtServer","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.BrokenPipeSocketException" }, { "name":"org.newsclub.net.unix.BufferOverflowTest", @@ -148,13 +544,16 @@ "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "methods":[ - {"name":"readOutOfBounds","parameterTypes":[] }, - {"name":"readUpTo","parameterTypes":[] }, - {"name":"setUp","parameterTypes":[] }, - {"name":"tearDown","parameterTypes":[] }, - {"name":"writeOverflow","parameterTypes":[] } - ] + "methods":[{"name":"readOutOfBounds","parameterTypes":[] }, {"name":"readUpTo","parameterTypes":[] }, {"name":"setUp","parameterTypes":[] }, {"name":"tearDown","parameterTypes":[] }, {"name":"writeOverflow","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.BuildPropertiesTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"testHasProperties","parameterTypes":[] }, {"name":"testNotEmpty","parameterTypes":[] }, {"name":"testResolved","parameterTypes":[] }] }, { "name":"org.newsclub.net.unix.CancelAcceptTest", @@ -164,17 +563,16 @@ "queryAllPublicMethods":true, "methods":[{"name":"issue6test1","parameterTypes":[] }] }, +{ + "name":"org.newsclub.net.unix.ConnectionResetSocketException" +}, { "name":"org.newsclub.net.unix.DatagramSocketTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "methods":[ - {"name":"testBindConnect","parameterTypes":[] }, - {"name":"testPeekTimeout","parameterTypes":[] }, - {"name":"testReadTimeout","parameterTypes":[] } - ] + "methods":[{"name":"testBindConnect","parameterTypes":[] }, {"name":"testChannelSendTo","parameterTypes":[] }, {"name":"testPeekTimeout","parameterTypes":[] }, {"name":"testReadTimeout","parameterTypes":[] }] }, { "name":"org.newsclub.net.unix.EndOfFileTest", @@ -182,17 +580,7 @@ "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "methods":[ - {"name":"bidirectionalSanity","parameterTypes":[] }, - {"name":"clientReadEof","parameterTypes":[] }, - {"name":"clientWriteToSocketClosedByClient","parameterTypes":[] }, - {"name":"clientWriteToSocketClosedByServer","parameterTypes":[] }, - {"name":"serverReadEof","parameterTypes":[] }, - {"name":"serverWriteToSocketClosedByClient","parameterTypes":[] }, - {"name":"serverWriteToSocketClosedByServer","parameterTypes":[] }, - {"name":"setUp","parameterTypes":[] }, - {"name":"tearDown","parameterTypes":[] } - ] + "methods":[{"name":"bidirectionalSanity","parameterTypes":[] }, {"name":"clientReadEof","parameterTypes":[] }, {"name":"clientWriteToSocketClosedByClient","parameterTypes":[] }, {"name":"clientWriteToSocketClosedByServer","parameterTypes":[] }, {"name":"serverReadEof","parameterTypes":[] }, {"name":"serverWriteToSocketClosedByClient","parameterTypes":[] }, {"name":"serverWriteToSocketClosedByServer","parameterTypes":[] }, {"name":"setUp","parameterTypes":[] }, {"name":"tearDown","parameterTypes":[] }] }, { "name":"org.newsclub.net.unix.FileDescriptorCastTest", @@ -201,14 +589,7 @@ "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, "queryAllDeclaredConstructors":true, - "methods":[ - {"name":"","parameterTypes":[] }, - {"name":"testAvailableTypes","parameterTypes":[] }, - {"name":"testInvalidFileDescriptor","parameterTypes":[] }, - {"name":"testPipe","parameterTypes":[] }, - {"name":"testRandomAccessFile","parameterTypes":[] }, - {"name":"testStdout","parameterTypes":[] } - ] + "methods":[{"name":"","parameterTypes":[] }, {"name":"testAvailableTypes","parameterTypes":[] }, {"name":"testCastAsInteger","parameterTypes":[] }, {"name":"testCastGeneric","parameterTypes":[] }, {"name":"testCastGenericDuplicating","parameterTypes":[] }, {"name":"testCastToServerSocketIsSameType","parameterTypes":[] }, {"name":"testInvalidFileDescriptor","parameterTypes":[] }, {"name":"testPipe","parameterTypes":[] }, {"name":"testRandomAccessFile","parameterTypes":[] }, {"name":"testStdout","parameterTypes":[] }, {"name":"testUnsafeCast","parameterTypes":[] }] }, { "name":"org.newsclub.net.unix.InetAddressTest", @@ -217,13 +598,39 @@ "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, "queryAllDeclaredConstructors":true, - "methods":[ - {"name":"","parameterTypes":[] }, - {"name":"testFromToBytes","parameterTypes":[] }, - {"name":"testHostnameString","parameterTypes":[] }, - {"name":"testHostnameStringEndsWithJunixSocket","parameterTypes":[] }, - {"name":"testIsLoopbackAddress","parameterTypes":[] } - ] + "methods":[{"name":"","parameterTypes":[] }, {"name":"testFromToBytes","parameterTypes":[] }, {"name":"testHostnameString","parameterTypes":[] }, {"name":"testHostnameStringEndsWithJunixSocket","parameterTypes":[] }, {"name":"testIsLoopbackAddress","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.InterruptIssue158Test", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "methods":[{"name":"afterEach","parameterTypes":[] }, {"name":"beforeEach","parameterTypes":["org.junit.jupiter.api.TestInfo"] }, {"name":"clientProvider","parameterTypes":[] }, {"name":"serverProvider","parameterTypes":[] }, {"name":"testClientInterruption","parameterTypes":["boolean","org.newsclub.net.unix.InterruptIssue158Test$IOSupplier","org.newsclub.net.unix.InterruptIssue158Test$IOConsumer","java.lang.Class","java.util.function.Predicate"] }, {"name":"testClientInterruptionWithDelay","parameterTypes":["boolean","org.newsclub.net.unix.InterruptIssue158Test$IOSupplier","org.newsclub.net.unix.InterruptIssue158Test$IOConsumer","java.lang.Class","java.util.function.Predicate"] }, {"name":"testServerInterruption","parameterTypes":["org.newsclub.net.unix.InterruptIssue158Test$IOSupplier","org.newsclub.net.unix.InterruptIssue158Test$IOConsumer","java.lang.Class","java.util.function.Predicate"] }, {"name":"testServerInterruptionWithDelay","parameterTypes":["org.newsclub.net.unix.InterruptIssue158Test$IOSupplier","org.newsclub.net.unix.InterruptIssue158Test$IOConsumer","java.lang.Class","java.util.function.Predicate"] }] +}, +{ + "name":"org.newsclub.net.unix.InterruptTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "methods":[{"name":"testInterruptSocketChannelVirtualThread","parameterTypes":[] }, {"name":"testInterruptSocketVirtualThread","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.InvalidArgumentSocketException" +}, +{ + "name":"org.newsclub.net.unix.MassiveParallelTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true +}, +{ + "name":"org.newsclub.net.unix.NoSuchDeviceSocketException" +}, +{ + "name":"org.newsclub.net.unix.OperationNotSupportedSocketException" }, { "name":"org.newsclub.net.unix.PipeTest", @@ -232,11 +639,7 @@ "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, "queryAllDeclaredConstructors":true, - "methods":[ - {"name":"","parameterTypes":[] }, - {"name":"testPipe","parameterTypes":[] }, - {"name":"testPipeRecvHang","parameterTypes":[] } - ] + "methods":[{"name":"","parameterTypes":[] }, {"name":"testPipe","parameterTypes":[] }, {"name":"testPipeRecvHang","parameterTypes":[] }] }, { "name":"org.newsclub.net.unix.ReadWriteTest", @@ -244,12 +647,7 @@ "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "methods":[ - {"name":"testReceiveDataByteForByteSendByteForByte","parameterTypes":[] }, - {"name":"testReceiveDataByteForByteSendWithByteArray","parameterTypes":[] }, - {"name":"testReceiveWithByteArraySendByteForByte","parameterTypes":[] }, - {"name":"testReceiveWithByteArraySendWithByteArray","parameterTypes":[] } - ] + "methods":[{"name":"testReceiveDataByteForByteSendByteForByte","parameterTypes":[] }, {"name":"testReceiveDataByteForByteSendWithByteArray","parameterTypes":[] }, {"name":"testReceiveWithByteArraySendByteForByte","parameterTypes":[] }, {"name":"testReceiveWithByteArraySendWithByteArray","parameterTypes":[] }] }, { "name":"org.newsclub.net.unix.SelectorTest", @@ -257,20 +655,11 @@ "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "methods":[ - {"name":"testCancelSelect","parameterTypes":[] }, - {"name":"testClosedSelectorSelect","parameterTypes":[] }, - {"name":"testClosedSelectorWakeup","parameterTypes":[] }, - {"name":"testConnectionCloseEventualClientDisconnect","parameterTypes":[] }, - {"name":"testConnectionCloseEventualClientDisconnectKeepLooping","parameterTypes":[] }, - {"name":"testConnectionCloseImmediateClientDisconnect","parameterTypes":[] }, - {"name":"testConnectionCloseImmediateClientDisconnectKeepLooping","parameterTypes":[] }, - {"name":"testNonBlockingAccept","parameterTypes":[] } - ] + "methods":[{"name":"testCancelSelect","parameterTypes":[] }, {"name":"testClosedSelectorSelect","parameterTypes":[] }, {"name":"testClosedSelectorWakeup","parameterTypes":[] }, {"name":"testConnectionCloseEventualClientDisconnect","parameterTypes":[] }, {"name":"testConnectionCloseEventualClientDisconnectKeepLooping","parameterTypes":[] }, {"name":"testConnectionCloseImmediateClientDisconnect","parameterTypes":[] }, {"name":"testConnectionCloseImmediateClientDisconnectKeepLooping","parameterTypes":[] }, {"name":"testNonBlockingAccept","parameterTypes":[] }] }, { "name":"org.newsclub.net.unix.SelftestDiagnosticsHelper", - "methods":[{"name":"initError","parameterTypes":[] }] + "methods":[{"name":"buildProperties","parameterTypes":[] }, {"name":"initError","parameterTypes":[] }] }, { "name":"org.newsclub.net.unix.ServerSocketCloseTest", @@ -278,141 +667,328 @@ "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "methods":[ - {"name":"testUnblockAcceptsWithSoTimeout","parameterTypes":[] }, - {"name":"testUnblockAcceptsWithoutSoTimeout","parameterTypes":[] } - ] + "methods":[{"name":"testUnblockAcceptsWithSoTimeout","parameterTypes":[] }, {"name":"testUnblockAcceptsWithoutSoTimeout","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.ServerSocketTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "methods":[{"name":"testBindBadArguments","parameterTypes":[] }, {"name":"testCloseable","parameterTypes":[] }, {"name":"testUnboundServerSocket","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.SoTimeoutTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "methods":[{"name":"issue14Fail","parameterTypes":[] }, {"name":"issue14Pass","parameterTypes":[] }, {"name":"testSocketTimeoutExceptionRead","parameterTypes":[] }, {"name":"testSocketTimeoutExceptionWrite","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.SocketChannelTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "methods":[{"name":"testAcceptNotBoundYet","parameterTypes":[] }, {"name":"testByteBufferWithPositionOffset","parameterTypes":[] }, {"name":"testDoubleBindAddressNotReusable","parameterTypes":[] }, {"name":"testDoubleBindAddressReusable","parameterTypes":[] }, {"name":"testNonBlockingConnect","parameterTypes":[] }, {"name":"testReadNotConnectedYet","parameterTypes":[] }, {"name":"testWriteNotConnectedYet","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.SocketClosedException" +}, +{ + "name":"org.newsclub.net.unix.SocketOptionsTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true +}, +{ + "name":"org.newsclub.net.unix.SocketPairTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "methods":[{"name":"testDatagramPair","parameterTypes":[] }, {"name":"testSocketPair","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.SocketTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "methods":[{"name":"testBindBadArguments","parameterTypes":[] }, {"name":"testCloseable","parameterTypes":[] }, {"name":"testConnectBadArguments","parameterTypes":[] }, {"name":"testUnconnectedSocket","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.SocketTestBase", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "methods":[{"name":"ensureSocketFileIsDeleted","parameterTypes":[] }, {"name":"tearDownClass","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.StandardSocketOptionsTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "methods":[{"name":"afterEach","parameterTypes":[] }, {"name":"beforeEach","parameterTypes":[] }, {"name":"testSocketOptions","parameterTypes":[] }, {"name":"testUnconnectedServerSocketOptions","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.TcpNoDelayTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "methods":[{"name":"testDefaultImpl","parameterTypes":[] }, {"name":"testStrictImpl","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.ThroughputTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "methods":[{"name":"testDatagramChannel","parameterTypes":[] }, {"name":"testDatagramChannelDirect","parameterTypes":[] }, {"name":"testDatagramChannelNonBlocking","parameterTypes":[] }, {"name":"testDatagramChannelNonBlockingDirect","parameterTypes":[] }, {"name":"testDatagramPacket","parameterTypes":[] }, {"name":"testSocket","parameterTypes":[] }, {"name":"testSocketChannel","parameterTypes":[] }, {"name":"testSocketChannelDirectBuffer","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.darwin.system.AFSYSTEMSelectorProvider" +}, +{ + "name":"org.newsclub.net.unix.darwin.system.KernelControlNamesTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"testStandardKernelControlNames","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.darwin.system.UtunTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.domain.AbstractNamespaceTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"org.newsclub.net.unix.domain.AcceptTimeoutTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.domain.AvailableTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.domain.BufferOverflowTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.domain.CancelAcceptTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.domain.DatagramSocketTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"testSeqPacket","parameterTypes":[] }, {"name":"testSeqPacketPair","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.domain.EndOfFileTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.domain.FileDescriptorCastTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"testDatagramFileChannel","parameterTypes":[] }, {"name":"testDatagramPorts","parameterTypes":[] }, {"name":"testDatagramSocket","parameterTypes":[] }, {"name":"testForkedVMRedirectStdin","parameterTypes":[] }, {"name":"testServer","parameterTypes":[] }, {"name":"testSocketPair","parameterTypes":[] }, {"name":"testSocketPairNative","parameterTypes":[] }, {"name":"testSocketPorts","parameterTypes":[] }, {"name":"testUnconnectedServerAsSocket","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.domain.FileDescriptorsTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"testAncillaryReceiveBufferTooSmall","parameterTypes":[] }, {"name":"testBadFileDescriptor","parameterTypes":[] }, {"name":"testDatagramSocket","parameterTypes":[] }, {"name":"testEmptyFileDescriptorArray","parameterTypes":[] }, {"name":"testFileInputStream","parameterTypes":[] }, {"name":"testFileInputStreamPartiallyConsumed","parameterTypes":[] }, {"name":"testNoAncillaryReceiveBuffer","parameterTypes":[] }, {"name":"testNullFileDescriptorArray","parameterTypes":[] }, {"name":"testSendRecvFileDescriptors","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.domain.InterruptIssue158Test", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.domain.InterruptTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.domain.MassiveParallelTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"testAcceptConnect","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.domain.PeerCredentialsTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"ensureSameCreds","parameterTypes":[] }, {"name":"testDatagramSocket","parameterTypes":[] }, {"name":"testSocketsSameProcess","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.ServerSocketTest", + "name":"org.newsclub.net.unix.domain.ReadWriteTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "methods":[ - {"name":"testBindBadArguments","parameterTypes":[] }, - {"name":"testCloseable","parameterTypes":[] }, - {"name":"testUnboundServerSocket","parameterTypes":[] } - ] + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.SoTimeoutTest", + "name":"org.newsclub.net.unix.domain.SelectorTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "methods":[ - {"name":"issue14Fail","parameterTypes":[] }, - {"name":"issue14Pass","parameterTypes":[] }, - {"name":"testSocketTimeoutExceptionRead","parameterTypes":[] }, - {"name":"testSocketTimeoutExceptionWrite","parameterTypes":[] } - ] + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.SocketChannelTest", + "name":"org.newsclub.net.unix.domain.ServerSocketCloseTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "methods":[ - {"name":"testDoubleBindAddressNotReusable","parameterTypes":[] }, - {"name":"testDoubleBindAddressReusable","parameterTypes":[] }, - {"name":"testNonBlockingConnect","parameterTypes":[] } - ] + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.SocketOptionsTest", + "name":"org.newsclub.net.unix.domain.ServerSocketTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, - "queryAllPublicMethods":true + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"testSupported","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.SocketPairTest", + "name":"org.newsclub.net.unix.domain.SoTimeoutTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "methods":[ - {"name":"testDatagramPair","parameterTypes":[] }, - {"name":"testSocketPair","parameterTypes":[] } - ] + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.SocketTest", + "name":"org.newsclub.net.unix.domain.SocketAddressTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "methods":[ - {"name":"testBindBadArguments","parameterTypes":[] }, - {"name":"testCloseable","parameterTypes":[] }, - {"name":"testConnectBadArguments","parameterTypes":[] }, - {"name":"testUnconnectedSocket","parameterTypes":[] } - ] + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"testAbstractNamespace","parameterTypes":[] }, {"name":"testByteConstructor","parameterTypes":[] }, {"name":"testEmptyAddress","parameterTypes":[] }, {"name":"testInetAddress","parameterTypes":[] }, {"name":"testLargePort","parameterTypes":[] }, {"name":"testLegacyConstructor","parameterTypes":[] }, {"name":"testPath","parameterTypes":[] }, {"name":"testPort","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.SocketTestBase", + "name":"org.newsclub.net.unix.domain.SocketChannelTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "methods":[ - {"name":"ensureSocketFileIsDeleted","parameterTypes":[] }, - {"name":"tearDownClass","parameterTypes":[] } - ] + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"testUnixDomainProtocolFamily","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.StandardSocketOptionsTest", + "name":"org.newsclub.net.unix.domain.SocketFactoryTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "methods":[ - {"name":"afterEach","parameterTypes":[] }, - {"name":"beforeEach","parameterTypes":[] }, - {"name":"testSocketOptions","parameterTypes":[] }, - {"name":"testUnconnectedServerSocketOptions","parameterTypes":[] } - ] + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"testFactoryArg","parameterTypes":[] }, {"name":"testReflection","parameterTypes":[] }, {"name":"testSystemProperty","parameterTypes":[] }, {"name":"testURISchemeCeateSocketThenConnect","parameterTypes":[] }, {"name":"testURISchemeCeateSocketWithHostnameValidCases","parameterTypes":[] }, {"name":"testURISchemeCeateSocketWithIllegalArguments","parameterTypes":[] }, {"name":"testURISchemeCeateSocketWithInvalidHostname","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.TcpNoDelayTest", + "name":"org.newsclub.net.unix.domain.SocketPairTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "methods":[ - {"name":"testDefaultImpl","parameterTypes":[] }, - {"name":"testStrictImpl","parameterTypes":[] } - ] + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.ThroughputTest", + "name":"org.newsclub.net.unix.domain.SocketTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "methods":[ - {"name":"testDatagramChannel","parameterTypes":[] }, - {"name":"testDatagramChannelDirect","parameterTypes":[] }, - {"name":"testDatagramChannelNonBlocking","parameterTypes":[] }, - {"name":"testDatagramChannelNonBlockingDirect","parameterTypes":[] }, - {"name":"testDatagramPacket","parameterTypes":[] }, - {"name":"testSocket","parameterTypes":[] }, - {"name":"testSocketChannel","parameterTypes":[] }, - {"name":"testSocketChannelDirectBuffer","parameterTypes":[] } - ] + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"testLoadedLibrary","parameterTypes":[] }, {"name":"testMain","parameterTypes":[] }, {"name":"testReceivedFileDescriptorsUnconnected","parameterTypes":[] }, {"name":"testSupported","parameterTypes":[] }, {"name":"testSupports","parameterTypes":[] }, {"name":"testVersion","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.AbstractNamespaceTest", + "name":"org.newsclub.net.unix.domain.StandardSocketOptionsTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "queryAllDeclaredConstructors":true + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.AcceptTimeoutTest", + "name":"org.newsclub.net.unix.domain.TcpNoDelayTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, @@ -421,7 +997,7 @@ "methods":[{"name":"","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.AvailableTest", + "name":"org.newsclub.net.unix.domain.ThroughputTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, @@ -430,25 +1006,27 @@ "methods":[{"name":"","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.BufferOverflowTest", + "name":"org.newsclub.net.unix.domain.ThroughputTestShim", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":[] }] + "methods":[{"name":"testJEP380","parameterTypes":[] }, {"name":"testJEP380direct","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.CancelAcceptTest", + "name":"org.newsclub.net.unix.domain.UnixDomainSocketAddressTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":[] }] + "methods":[{"name":"","parameterTypes":[] }, {"name":"testConvertUnixDomainSocketAddress","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.DatagramSocketTest", + "name":"org.newsclub.net.unix.generic.AFGenericSelectorProvider" +}, +{ + "name":"org.newsclub.net.unix.java.InterruptIssue158Test", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, @@ -457,7 +1035,7 @@ "methods":[{"name":"","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.EndOfFileTest", + "name":"org.newsclub.net.unix.java.InterruptTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, @@ -466,61 +1044,42 @@ "methods":[{"name":"","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.FileDescriptorCastTest", + "name":"org.newsclub.net.unix.java.JavaInetStackCondition", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.java.JavaInetStackRequirement", + "queryAllPublicMethods":true +}, +{ + "name":"org.newsclub.net.unix.java.SelectorTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, "queryAllDeclaredConstructors":true, - "methods":[ - {"name":"","parameterTypes":[] }, - {"name":"testDatagramFileChannel","parameterTypes":[] }, - {"name":"testDatagramPorts","parameterTypes":[] }, - {"name":"testDatagramSocket","parameterTypes":[] }, - {"name":"testForkedVMRedirectStdin","parameterTypes":[] }, - {"name":"testServer","parameterTypes":[] }, - {"name":"testSocketPair","parameterTypes":[] }, - {"name":"testSocketPairNative","parameterTypes":[] }, - {"name":"testSocketPorts","parameterTypes":[] }, - {"name":"testUnconnectedServerAsSocket","parameterTypes":[] } - ] + "methods":[{"name":"","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.FileDescriptorsTest", + "name":"org.newsclub.net.unix.java.SocketChannelTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, "queryAllDeclaredConstructors":true, - "methods":[ - {"name":"","parameterTypes":[] }, - {"name":"testAncillaryReceiveBufferTooSmall","parameterTypes":[] }, - {"name":"testBadFileDescriptor","parameterTypes":[] }, - {"name":"testDatagramSocket","parameterTypes":[] }, - {"name":"testEmptyFileDescriptorArray","parameterTypes":[] }, - {"name":"testFileInputStream","parameterTypes":[] }, - {"name":"testFileInputStreamPartiallyConsumed","parameterTypes":[] }, - {"name":"testNoAncillaryReceiveBuffer","parameterTypes":[] }, - {"name":"testNullFileDescriptorArray","parameterTypes":[] }, - {"name":"testSendRecvFileDescriptors","parameterTypes":[] } - ] + "methods":[{"name":"","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.PeerCredentialsTest", + "name":"org.newsclub.net.unix.java.ThroughputTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, "queryAllDeclaredConstructors":true, - "methods":[ - {"name":"","parameterTypes":[] }, - {"name":"ensureSameCreds","parameterTypes":[] }, - {"name":"testDatagramSocket","parameterTypes":[] }, - {"name":"testSocketsSameProcess","parameterTypes":[] } - ] + "methods":[{"name":"","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.ReadWriteTest", + "name":"org.newsclub.net.unix.jep380.InterruptIssue158Test", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, @@ -529,7 +1088,7 @@ "methods":[{"name":"","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.SelectorTest", + "name":"org.newsclub.net.unix.jep380.SocketChannelTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, @@ -538,131 +1097,192 @@ "methods":[{"name":"","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.ServerSocketCloseTest", + "name":"org.newsclub.net.unix.rmi.AFRMIService", + "queryAllPublicMethods":true +}, +{ + "name":"org.newsclub.net.unix.rmi.AFRMIServiceImpl_Stub" +}, +{ + "name":"org.newsclub.net.unix.rmi.AFRMISocketFactory" +}, +{ + "name":"org.newsclub.net.unix.rmi.AFRegistry$1_Stub" +}, +{ + "name":"org.newsclub.net.unix.rmi.AFSocketCapabilityCondition", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.rmi.AFSocketCapabilityRequirement", + "queryAllPublicMethods":true +}, +{ + "name":"org.newsclub.net.unix.rmi.AFUNIXRMISocketFactory" +}, +{ + "name":"org.newsclub.net.unix.rmi.Hello", + "queryAllPublicMethods":true, + "methods":[{"name":"hello","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.rmi.HelloImpl_Stub" +}, +{ + "name":"org.newsclub.net.unix.rmi.JunixsocketVersionTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":[] }] + "methods":[{"name":"","parameterTypes":[] }, {"name":"testVersion","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.ServerSocketTest", + "name":"org.newsclub.net.unix.rmi.NaiveFileInputStreamRemote", + "queryAllPublicMethods":true, + "methods":[{"name":"close","parameterTypes":[] }, {"name":"getRemoteFileDescriptor","parameterTypes":[] }, {"name":"read","parameterTypes":[] }, {"name":"readAllBytes","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.rmi.NaiveFileInputStreamRemoteImpl_Stub" +}, +{ + "name":"org.newsclub.net.unix.rmi.RMIPeerCredentialsTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, "queryAllDeclaredConstructors":true, - "methods":[ - {"name":"","parameterTypes":[] }, - {"name":"testSupported","parameterTypes":[] } - ] + "methods":[{"name":"","parameterTypes":[] }, {"name":"testRemotePeerCredentials","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.SoTimeoutTest", + "name":"org.newsclub.net.unix.rmi.RegistryTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":[] }] + "methods":[{"name":"","parameterTypes":[] }, {"name":"testDoubleCreateRegistry","parameterTypes":[] }, {"name":"testExportAndBind","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.SocketAddressTest", + "name":"org.newsclub.net.unix.rmi.RemoteCloseable", + "queryAllPublicMethods":true, + "methods":[{"name":"close","parameterTypes":[] }, {"name":"get","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.rmi.RemoteCloseableImpl_Stub" +}, +{ + "name":"org.newsclub.net.unix.rmi.RemoteCloseableTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, "queryAllDeclaredConstructors":true, - "methods":[ - {"name":"","parameterTypes":[] }, - {"name":"testAbstractNamespace","parameterTypes":[] }, - {"name":"testByteConstructor","parameterTypes":[] }, - {"name":"testEmptyAddress","parameterTypes":[] }, - {"name":"testInetAddress","parameterTypes":[] }, - {"name":"testLegacyConstructor","parameterTypes":[] }, - {"name":"testPath","parameterTypes":[] }, - {"name":"testPort","parameterTypes":[] } - ] + "methods":[{"name":"","parameterTypes":[] }, {"name":"testRemoteCloseableWithACloseableThing","parameterTypes":[] }, {"name":"testRemoteCloseableWithANotCloseableThing","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.SocketChannelTest", + "name":"org.newsclub.net.unix.rmi.RemoteCloseableThing$IsCloseable" +}, +{ + "name":"org.newsclub.net.unix.rmi.RemoteCloseableThing$NotCloseable" +}, +{ + "name":"org.newsclub.net.unix.rmi.RemoteCloseableThingImpl" +}, +{ + "name":"org.newsclub.net.unix.rmi.RemoteCloseableThingImpl$IsCloseableImpl" +}, +{ + "name":"org.newsclub.net.unix.rmi.RemoteCloseableThingImpl$NotCloseableImpl" +}, +{ + "name":"org.newsclub.net.unix.rmi.RemoteFileDescriptor" +}, +{ + "name":"org.newsclub.net.unix.rmi.RemoteFileDescriptorBase" +}, +{ + "name":"org.newsclub.net.unix.rmi.RemoteFileDescriptorTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":[] }] + "methods":[{"name":"","parameterTypes":[] }, {"name":"testFindSocketFactory","parameterTypes":[] }, {"name":"testReadWrite","parameterTypes":[] }, {"name":"testRemoteStdout","parameterTypes":[] }, {"name":"testRemoteStdoutNoop","parameterTypes":[] }, {"name":"testServiceProxy","parameterTypes":[] }, {"name":"testWriteAndReadHello","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.SocketFactoryTest", + "name":"org.newsclub.net.unix.rmi.RemoteFileInput" +}, +{ + "name":"org.newsclub.net.unix.rmi.RemoteFileOutput" +}, +{ + "name":"org.newsclub.net.unix.rmi.ShutdownHookTestBase", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "queryAllDeclaredConstructors":true, - "methods":[ - {"name":"","parameterTypes":[] }, - {"name":"testFactoryArg","parameterTypes":[] }, - {"name":"testSystemProperty","parameterTypes":[] }, - {"name":"testURISchemeCeateSocketThenConnect","parameterTypes":[] }, - {"name":"testURISchemeCeateSocketWithHostnameValidCases","parameterTypes":[] }, - {"name":"testURISchemeCeateSocketWithIllegalArguments","parameterTypes":[] }, - {"name":"testURISchemeCeateSocketWithInvalidHostname","parameterTypes":[] } - ] + "methods":[{"name":"tearDownClass","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.SocketPairTest", + "name":"org.newsclub.net.unix.rmi.TestBase", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, - "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":[] }] + "methods":[{"name":"setUp","parameterTypes":[] }, {"name":"tearDown","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.SocketTest", + "name":"org.newsclub.net.unix.rmi.TestService", + "queryAllPublicMethods":true, + "methods":[{"name":"input","parameterTypes":[] }, {"name":"input","parameterTypes":["long"] }, {"name":"naiveInputStreamRemote","parameterTypes":[] }, {"name":"output","parameterTypes":[] }, {"name":"remoteCloseable","parameterTypes":["java.lang.Class"] }, {"name":"remoteCloseableThingNumberOfCloseCalls","parameterTypes":["java.lang.Class"] }, {"name":"remoteCloseableThingResetNumberOfCloseCalls","parameterTypes":["java.lang.Class"] }, {"name":"remotePeerCredentials","parameterTypes":[] }, {"name":"stdout","parameterTypes":[] }, {"name":"verifyContents","parameterTypes":["byte[]"] }] +}, +{ + "name":"org.newsclub.net.unix.rmi.TestServiceImpl_Stub" +}, +{ + "name":"org.newsclub.net.unix.ssl.ProviderTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, "queryAllDeclaredConstructors":true, - "methods":[ - {"name":"","parameterTypes":[] }, - {"name":"testLoadedLibrary","parameterTypes":[] }, - {"name":"testMain","parameterTypes":[] }, - {"name":"testReceivedFileDescriptorsUnconnected","parameterTypes":[] }, - {"name":"testSupported","parameterTypes":[] }, - {"name":"testSupports","parameterTypes":[] }, - {"name":"testVersion","parameterTypes":[] } - ] + "methods":[{"name":"","parameterTypes":[] }, {"name":"testDumpDefaultProviders","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.StandardSocketOptionsTest", + "name":"org.newsclub.net.unix.ssl.SNIHostnameCaptureTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":[] }] + "methods":[{"name":"","parameterTypes":[] }, {"name":"testSNISuccessExpectDefault_NO_serverNullDefault_NO_clientEmptyDefault_NO","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testSNISuccessExpectDefault_NO_serverNullDefault_NO_clientEmptyDefault_YES","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testSNISuccessExpectDefault_NO_serverNullDefault_YES_clientEmptyDefault_NO","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testSNISuccessExpectDefault_NO_serverNullDefault_YES_clientEmptyDefault_YES","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testSNISuccessExpectDefault_YES_serverNullDefault_NO_clientEmptyDefault_NO","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testSNISuccessExpectDefault_YES_serverNullDefault_NO_clientEmptyDefault_YES","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testSNISuccessExpectDefault_YES_serverNullDefault_YES_clientEmptyDefault_NO","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testSNISuccessExpectDefault_YES_serverNullDefault_YES_clientEmptyDefault_YES","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testWonkySSLSocket","parameterTypes":[] }] }, { - "name":"org.newsclub.net.unix.domain.TcpNoDelayTest", + "name":"org.newsclub.net.unix.ssl.SSLContextBuilderTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":[] }] + "methods":[{"name":"","parameterTypes":[] }, {"name":"testBadProtocol","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testBothKeyManagersAndKeyStore","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testBothKeyStoreAndKeyManagers","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testBothTrustManagersAndTrustStore","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testBothTrustStoreAndTrustManagers","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testClientAuthRequired","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testClientAuthRequiredButClientIsNotSendingAKey","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testClientAuthRequiredButClientKeyIsNotTrusted","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testClientHasNoTrustStore","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testDestroyablePasswordSupplier","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testDestroyablePasswordSupplierDestroyed","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testKeyStoreFileNotFound","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testKeyStoreNullPasswordSupplied","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testKeyStoreNullPasswordSupplier","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testKeyStoreNullURL","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testKeyStoreURLNotFound","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testNoClientAuth","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testSSLEngineMethods","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testServerAndClientBlindlyTrustAnything","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testServerSocketFactoryMethods","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testServerSocketFactoryMethodsForCodeCoverageOnly","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testSocketFactoryMethods","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testSocketFactoryMethodsForCodeCoverageOnly","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testTrustStoreFileNotFound","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testTrustStoreNullPasswordSupplied","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testTrustStoreNullPasswordSupplier","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testTrustStoreNullURL","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testTrustStoreURLNotFound","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testUndestroyablePasswordSupplier","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testUndestroyablePasswordSuppliers","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testWithDefaultParameters1","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testWithDefaultParameters2","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }] }, { - "name":"org.newsclub.net.unix.domain.ThroughputTest", + "name":"org.newsclub.net.unix.ssl.SSLTestBase", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "methods":[{"name":"afterAll","parameterTypes":[] }, {"name":"beforeAll","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.ssl.ValidatingX509TrustManagerTest", "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":[] }] + "methods":[{"name":"","parameterTypes":[] }, {"name":"testInspectTrustedCertificateExpired","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testInspectTrustedCertificateExpiredNested","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testInspectTrustedCertificateExpiredNestedFilter","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testInspectTrustedCertificateNotExpired","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testInspectTrustedClientCertificate","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testInspectTrustedClientCertificateExpired","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }, {"name":"testInspectTrustedClientCertificateExpiredNested","parameterTypes":["org.newsclub.net.unix.ssl.SSLTestBase$TestSSLConfiguration"] }] }, { "name":"org.newsclub.net.unix.tipc.AFTIPCSelectorProvider" @@ -834,10 +1454,7 @@ "queryAllDeclaredMethods":true, "queryAllPublicMethods":true, "queryAllDeclaredConstructors":true, - "methods":[ - {"name":"","parameterTypes":[] }, - {"name":"testGetLocalID","parameterTypes":[] } - ] + "methods":[{"name":"","parameterTypes":[] }, {"name":"testGetLocalID","parameterTypes":[] }] }, { "name":"org.newsclub.net.unix.vsock.AFVSOCKSelectorProvider" @@ -991,11 +1608,172 @@ "queryAllDeclaredConstructors":true }, { - "name":"sun.security.provider.NativePRNG", + "name":"org.openjsse.net.ssl.OpenJSSE" +}, +{ + "name":"sun.rmi.registry.RegistryImpl_Skel", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.rmi.registry.RegistryImpl_Stub", + "methods":[{"name":"","parameterTypes":["java.rmi.server.RemoteRef"] }] +}, +{ + "name":"sun.rmi.server.UnicastRef2", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.rmi.transport.DGCImpl_Skel", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.rmi.transport.DGCImpl_Stub", + "methods":[{"name":"","parameterTypes":["java.rmi.server.RemoteRef"] }] +}, +{ + "name":"sun.security.pkcs12.PKCS12KeyStore", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.DSA$SHA224withDSA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.DSA$SHA256withDSA", "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"sun.security.provider.NativePRNG", + "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] +}, +{ + "name":"sun.security.provider.NativePRNG$NonBlocking", + "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] +}, { "name":"sun.security.provider.SHA", "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA2$SHA224", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA2$SHA256", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA5$SHA384", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA5$SHA512", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.X509Factory", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.certpath.CollectionCertStore", + "methods":[{"name":"","parameterTypes":["java.security.cert.CertStoreParameters"] }] +}, +{ + "name":"sun.security.provider.certpath.SunCertPathBuilder", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.PSSParameters", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSAKeyFactory$Legacy", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSAPSSSignature", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSASignature$SHA224withRSA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSASignature$SHA384withRSA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.KeyManagerFactoryImpl$SunX509", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.SSLContextImpl$DefaultSSLContext", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.SSLContextImpl$TLSContext", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.util.ObjectIdentifier" +}, +{ + "name":"sun.security.x509.AuthorityInfoAccessExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.AuthorityKeyIdentifierExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.BasicConstraintsExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.CRLDistributionPointsExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.CertificateExtensions" +}, +{ + "name":"sun.security.x509.CertificatePoliciesExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.ExtendedKeyUsageExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.IssuerAlternativeNameExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.KeyUsageExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.NetscapeCertTypeExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.PrivateKeyUsageExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.SubjectAlternativeNameExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.SubjectKeyIdentifierExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] } ] \ No newline at end of file diff --git a/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/resource-config.json b/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/resource-config.json index dca265359..f8e29646d 100644 --- a/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/resource-config.json +++ b/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/resource-config.json @@ -1,27 +1,86 @@ { "resources":{ - "includes":[ - { - "pattern":"\\QMETA-INF/maven/com.kohlschutter.junixsocket/junixsocket-common/pom.properties\\E" - }, - { - "pattern":"\\QMETA-INF/maven/com.kohlschutter.junixsocket/junixsocket-native-common/pom.properties\\E" - }, - { - "pattern":"\\QMETA-INF/maven/com.kohlschutter.junixsocket/junixsocket-native-custom/pom.properties\\E" - }, - { - "pattern":"\\QMETA-INF/maven/com.kohlschutter.junixsocket/junixsocket-selftest/git.properties\\E" - }, - { - "pattern":"\\QMETA-INF/services/org.junit.platform.engine.TestEngine\\E" - }, - { - "pattern":"\\QMETA-INF/services/org.junit.platform.launcher.TestExecutionListener\\E" - }, - { - "pattern":"\\Qlib/aarch64-MacOSX-clang/jni/libjunixsocket-native-2.6.1-SNAPSHOT.dylib\\E" - } - ]}, - "bundles":[] + "includes":[{ + "pattern":"\\QMETA-INF/maven/com.kohlschutter.junixsocket/junixsocket-common/pom.properties\\E" + }, { + "pattern":"\\QMETA-INF/maven/com.kohlschutter.junixsocket/junixsocket-native-common/pom.properties\\E" + }, { + "pattern":"\\QMETA-INF/maven/com.kohlschutter.junixsocket/junixsocket-native-custom/pom.properties\\E" + }, { + "pattern":"\\QMETA-INF/maven/com.kohlschutter.junixsocket/junixsocket-selftest/git.properties\\E" + }, { + "pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, { + "pattern":"\\QMETA-INF/services/java.net.spi.InetAddressResolverProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.net.spi.URLStreamHandlerProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.nio.channels.spi.SelectorProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.rmi.server.RMIClassLoaderSpi\\E" + }, { + "pattern":"\\QMETA-INF/services/java.sql.Driver\\E" + }, { + "pattern":"\\QMETA-INF/services/java.util.spi.ResourceBundleControlProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener\\E" + }, { + "pattern":"\\QMETA-INF/services/org.junit.platform.launcher.LauncherSessionListener\\E" + }, { + "pattern":"\\QMETA-INF/services/org.junit.platform.launcher.PostDiscoveryFilter\\E" + }, { + "pattern":"\\QMETA-INF/services/org.junit.platform.launcher.TestExecutionListener\\E" + }, { + "pattern":"\\QMETA-INF/services/org/jline/terminal/provider/jansi\\E" + }, { + "pattern":"\\Q\\E" + }, { + "pattern":"\\Qcom/mysql/cj/LocalizedErrorMessages.properties\\E" + }, { + "pattern":"\\Qcom/mysql/cj/LocalizedErrorMessages_en.properties\\E" + }, { + "pattern":"\\Qcom/mysql/cj/LocalizedErrorMessages_en_US.properties\\E" + }, { + "pattern":"\\Qjunit-platform.properties\\E" + }, { + "pattern":"\\Qlib/aarch64-MacOSX-clang/jni/libjunixsocket-native-2.10.0-SNAPSHOT.dylib\\E" + }, { + "pattern":"\\Qlib/aarch64-MacOSX-clang/jni/libjunixsocket-native-2.10.0-SNAPSHOT.nodeps.dylib\\E" + }, { + "pattern":"\\Qlib/aarch64-MacOSX-gcc/jni/libjunixsocket-native-2.10.0-SNAPSHOT.dylib\\E" + }, { + "pattern":"\\Qlib/aarch64-MacOSX-gcc/jni/libjunixsocket-native-2.10.0-SNAPSHOT.nodeps.dylib\\E" + }, { + "pattern":"\\Qorg/newsclub/net/unix/ssl/juxclient.p12\\E" + }, { + "pattern":"\\Qorg/newsclub/net/unix/ssl/juxclient.truststore\\E" + }, { + "pattern":"\\Qorg/newsclub/net/unix/ssl/juxserver.p12\\E" + }, { + "pattern":"\\Qorg/newsclub/net/unix/ssl/juxserver.truststore\\E" + }, { + "pattern":"\\Qorg/newsclub/net/unix/ssl/logging.properties\\E" + }, { + "pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt74b/nfc.nrm\\E" + }, { + "pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt74b/nfkc.nrm\\E" + }, { + "pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt74b/uprops.icu\\E" + }, { + "pattern":"java.base:\\Qsun/net/idn/uidna.spp\\E" + }, { + "pattern":"jdk.internal.le:\\Qjdk/internal/org/jline/utils/capabilities.txt\\E" + }, { + "pattern":"jdk.internal.le:\\Qjdk/internal/org/jline/utils/xterm-256color.caps\\E" + }]}, + "bundles":[{ + "name":"com.mysql.cj.LocalizedErrorMessages", + "locales":["en-US"] + }, { + "name":"sun.text.resources.cldr.FormatData", + "locales":["en", "en-US", "und"] + }, { + "name":"sun.util.resources.cldr.TimeZoneNames", + "locales":["en", "en-US", "und"] + }] } \ No newline at end of file diff --git a/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/serialization-config.json b/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/serialization-config.json index d35fed355..62201fb44 100644 --- a/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/serialization-config.json +++ b/junixsocket-native-graalvm/output/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-native-graalvm/serialization-config.json @@ -1,12 +1,128 @@ { "types":[ { - "name":"java.net.InetAddress" + "name":"byte[]" + }, + { + "name":"java.io.IOException" + }, + { + "name":"java.lang.Exception" + }, + { + "name":"java.lang.Number" + }, + { + "name":"java.lang.StackTraceElement[]" + }, + { + "name":"java.lang.String" + }, + { + "name":"java.lang.Throwable" + }, + { + "name":"java.lang.reflect.Proxy" + }, + { + "name":"java.net.InetSocketAddress" + }, + { + "name":"java.net.SocketAddress" + }, + { + "name":"java.rmi.NoSuchObjectException" + }, + { + "name":"java.rmi.RemoteException" + }, + { + "name":"java.rmi.dgc.Lease" + }, + { + "name":"java.rmi.dgc.VMID" + }, + { + "name":"java.rmi.server.ObjID" + }, + { + "name":"java.rmi.server.ObjID[]" + }, + { + "name":"java.rmi.server.RemoteObject" + }, + { + "name":"java.rmi.server.RemoteObjectInvocationHandler" + }, + { + "name":"java.rmi.server.UID" + }, + { + "name":"java.util.Collections$EmptyList" + }, + { + "name":"java.util.UUID" + }, + { + "name":"java.util.concurrent.atomic.AtomicInteger" + }, + { + "name":"long[]" }, { "name":"org.junit.platform.launcher.TestIdentifier$SerializedForm" + }, + { + "name":"org.newsclub.net.unix.AFSocketAddress" + }, + { + "name":"org.newsclub.net.unix.AFUNIXSocketAddress" + }, + { + "name":"org.newsclub.net.unix.AFUNIXSocketCredentials" + }, + { + "name":"org.newsclub.net.unix.rmi.AFRMISocketFactory" + }, + { + "name":"org.newsclub.net.unix.rmi.AFUNIXRMISocketFactory" + }, + { + "name":"org.newsclub.net.unix.rmi.RemoteCloseableThing$IsCloseable" + }, + { + "name":"org.newsclub.net.unix.rmi.RemoteCloseableThing$NotCloseable" + }, + { + "name":"org.newsclub.net.unix.rmi.RemoteCloseableThingImpl" + }, + { + "name":"org.newsclub.net.unix.rmi.RemoteCloseableThingImpl$IsCloseableImpl" + }, + { + "name":"org.newsclub.net.unix.rmi.RemoteCloseableThingImpl$NotCloseableImpl" + }, + { + "name":"org.newsclub.net.unix.rmi.RemoteFileDescriptor" + }, + { + "name":"org.newsclub.net.unix.rmi.RemoteFileDescriptorBase" + }, + { + "name":"org.newsclub.net.unix.rmi.RemoteFileInput" + }, + { + "name":"org.newsclub.net.unix.rmi.RemoteFileOutput" } ], "lambdaCapturingTypes":[ + ], + "proxies":[ + { + "interfaces":["org.newsclub.net.unix.rmi.NaiveFileInputStreamRemote"] + }, + { + "interfaces":["org.newsclub.net.unix.rmi.RemoteCloseable"] + } ] } \ No newline at end of file diff --git a/junixsocket-native/.gitignore b/junixsocket-native/.gitignore index 7b9e6c964..0303a4cdb 100644 --- a/junixsocket-native/.gitignore +++ b/junixsocket-native/.gitignore @@ -1,3 +1,7 @@ /*.xcodeproj/xcuserdata /*.xcodeproj/project.xcworkspace/xcuserdata /target-eclipse/ +src/**/*.jar +/build +/*.log +/Makefile diff --git a/junixsocket-native/crossclang/Xcode-Support/crossclang.sdk/SDKSettings.plist b/junixsocket-native/crossclang/Xcode-Support/crossclang.sdk/SDKSettings.plist index c44fded3b..5bae39aec 100644 Binary files a/junixsocket-native/crossclang/Xcode-Support/crossclang.sdk/SDKSettings.plist and b/junixsocket-native/crossclang/Xcode-Support/crossclang.sdk/SDKSettings.plist differ diff --git a/junixsocket-native/crossclang/Xcode-Support/install b/junixsocket-native/crossclang/Xcode-Support/install index a7d4d99a7..b17a67bc1 100755 --- a/junixsocket-native/crossclang/Xcode-Support/install +++ b/junixsocket-native/crossclang/Xcode-Support/install @@ -2,23 +2,11 @@ # # Installs optional Xcode helper components for crossclang # -# You should run this as root, i.e.,: sudo ./install -# cd "$(dirname $0)" pwd=$(pwd) -developerPath=$(xcode-select -p) -if [[ -z "$developerPath" ]]; then - echo "Error: Could not resolve XCode Developer path" >&2 - exit 1 -fi -sdksPath="$developerPath/Platforms/MacOSX.platform/Developer/SDKs" -if [[ ! -d "$sdksPath" ]]; then - echo "Error: Could not find SDKs path: $sdksPath" >&2 - exit 1 -fi -toolchainsPath="/Library/Developer/Toolchains" +toolchainsPath="$HOME/Library/Developer/Toolchains" echo "Toolchains path: $toolchainsPath" if [[ ! -d "$toolchainsPath" ]]; then mkdir -pv "$toolchainsPath" @@ -30,14 +18,8 @@ for toolchain in $(ls -1 | grep '\.xctoolchain$'); do done echo -echo "SDKs path: $sdksPath" -for sdk in $(ls -1 | grep '\.sdk$'); do - echo Installing "$sdk" ... - ( cd "$sdksPath" ; ln -sf "$pwd/$sdk" ) -done - if [ $? -eq 0 ]; then - echo Done. Now please restart XCode and run \"Clean Build Folder\". + echo "Done. Now please restart XCode, set the Toolchain (Xcode->Toolchains..., or via the launch window) to \"crossclang\", then run \"Clean Build Folder\"." else - echo There were errors. Try running with sudo. + echo "There were errors." >&2 fi diff --git a/junixsocket-native/crossclang/bin/clang b/junixsocket-native/crossclang/bin/clang index 97aa0cfef..37c72661c 100755 --- a/junixsocket-native/crossclang/bin/clang +++ b/junixsocket-native/crossclang/bin/clang @@ -80,6 +80,9 @@ hideUnknownWarningWarnings=0 hasExportDynamic=0 ignoreExportDynamic=0 +hasReproducible=0 +ignoreReproducible=0 + skipGccArg=0 analyze=0 @@ -97,7 +100,17 @@ while [ $# -gt 0 ]; do --serialize-diagnostics ) serializeDiagnosticsOut="$v" ;; esac - case "$v" in + case "$v" in + @* ) + argFile=${v:1} + if [[ -f "$argFile" ]]; then + OLDIFS=$IFS + IFS=$'\n' + set -- $(cat -- "$argFile" | xargs -n1) $@ + IFS=$OLDIFS + continue + fi + ;; -E ) needLinker=0 ;; -fsyntax-only ) needLinker=0 ;; -c ) needLinker=0 ;; @@ -111,10 +124,12 @@ while [ $# -gt 0 ]; do -Xcrossclang-llvm-name ) llvmName="$1"; shift ; skipArg=1 ;; -Xcrossclang-use-ldshim ) useLdShim=1 ; skipArg=1 ;; -Xcrossclang-use-gcc=* ) gccMode=1 ; compiler="${v#-Xcrossclang-use-gcc=}" ; skipArg=1 ;; + -Xcrossclang-use-clang=* ) compiler="${v#-Xcrossclang-use-clang=}" ; skipArg=1 ;; -Xcrossclang-output-strip-lib-prefix ) outputStripLibPrefix=1 ; skipArg=1 ;; - -Xcrossclang-ld64-sdk-version ) ld64_sdk_version="$1"; shift ; skipArg=1 ;; + -Xcrossclang-ld64-sdk-version ) if [[ "$1" != "undefined" ]]; then ld64_sdk_version="$1"; fi; shift ; skipArg=1 ;; -Xcrossclang-hide-unknown-warning-warnings ) hideUnknownWarningWarnings=1 ; skipArg=1 ;; -Xcrossclang-ignore-export-dynamic) ignoreExportDynamic=1 ; skipArg=1 ;; + -Xcrossclang-ignore-reproducible) ignoreReproducible=1 ; skipArg=1 ;; # -index-store-path ) shift ; skipArg=1 ;; # if you're hitting this, set "Enable Index-While-Building Functionality=No" in Xcode build settings instead -filelist ) fileList="$1" ; shift; skipArg=1 ;; -current_version ) shift ; skipArg=1 ;; @@ -132,9 +147,12 @@ while [ $# -gt 0 ]; do -Wbool-conversion ) skipGccArg=1 ;; -Wconstant-conversion ) skipGccArg=1 ;; -Wquoted-include-in-framework-header ) skipGccArg=1 ;; + -Wno-unsafe-buffer-usage ) skipGccArg=1 ;; -fno-c++-static-destructors ) skipGccArg=1 ;; -fmacro-backtrace-limit=0 ) skipGccArg=1 ;; -fdiagnostics-show-note-include-stack ) skipGccArg=1 ;; + -fcolor-diagnostics ) skipGccArg=1 ;; + -fno-color-diagnostics ) skipGccArg=1 ;; -Xclang ) skipGccArg=2 ;; # has an argument --analyze ) analyze=1 ;; -install_name ) skipGccArg=2 ;; # has an argument @@ -155,6 +173,11 @@ while [ $# -gt 0 ]; do -Xlinker ) if [[ "$1" == "-export_dynamic" ]]; then hasExportDynamic=1 + elif [[ "$1" == "-reproducible" ]]; then + hasReproducible=1 + elif [[ "$1" == "-no_adhoc_codesign" ]]; then + # ignore + skipArg=1 elif [[ -n "$1" ]]; then clangArgs+=("-Xlinker" "$1") gccArgs+=("-Xlinker" "$1") @@ -186,6 +209,11 @@ if [[ $hasExportDynamic -eq 1 && $ignoreExportDynamic -eq 0 ]]; then gccArgs+=("-Xlinker" "-export_dynamic") fi +if [[ $hasReproducible -eq 1 && $ignoreReproducible -eq 0 ]]; then + clangArgs+=("-Xlinker" "-reproducible") + # gccArgs+=("-Xlinker" "-reproducible") +fi + if [[ $analyze -gt 0 && $gccMode -gt 0 ]]; then # When using the static code analyzer, switch back to clang compiler=clang @@ -262,13 +290,14 @@ if [[ $gccMode -gt 0 ]]; then clangArgs=( ${gccArgs[@]} ) fi -whichComp=$(which $compiler) +whichComp=$(which "$compiler") if [[ -z "$whichComp" ]]; then echo "crossclang could not find compiler $compiler in path $PATH, giving up" >&2 exit 1 fi -clangDir=$(cd $(dirname "$whichComp"); pwd) +clangDir=$(dirname "$whichComp") +clangDir=$(cd "$clangDir"; pwd) if [ "$currentDir" == "$clangDir" ]; then echo "crossclang detected in PATH. Can't find real clang, giving up" >&2 exit 1 @@ -627,13 +656,14 @@ if [[ $gccMode -gt 0 ]]; then fi -compilerBinary="$(which $compiler)" +compilerBinary=$(which "$compiler") if [ -z "$compilerBinary" ]; then echo "Cannot compile -- compiler not found: $compiler" >&2 exit 1 fi # set -x +( if [[ $withAndWithoutLC -eq 1 ]]; then nodepsOut="${outputFile%.*}.nodeps.${outputFile##*.}" @@ -655,7 +685,8 @@ if [[ $withAndWithoutLC -eq 1 ]]; then "$compilerBinary" ${isystem_decls[@]} ${clangLinkerArgs[@]} ${wwoArgs[@]} ${clangArgs[@]} ${libsArgs[@]} && \ "$compilerBinary" ${isystem_decls[@]} ${clangLinkerArgs[@]} ${wwoArgsNoDeps[@]} ${clangArgsNoDeps[@]} ${libsArgs[@]} elif [[ $hideUnknownWarningWarnings -eq 1 ]]; then - exec "$compilerBinary" ${isystem_decls[@]} ${clangLinkerArgs[@]} ${output_args[@]} ${clangArgs[@]} ${libsArgs[@]} 2> >(grep -v -E "unrecognized command-line option .* may have been intended to silence") + exec "$compilerBinary" ${isystem_decls[@]} ${clangLinkerArgs[@]} ${output_args[@]} ${clangArgs[@]} ${libsArgs[@]} else exec "$compilerBinary" ${isystem_decls[@]} ${clangLinkerArgs[@]} ${output_args[@]} ${clangArgs[@]} ${libsArgs[@]} fi +) 2> >(grep -v -E "unrecognized command-line option .* may have been intended to silence") diff --git a/junixsocket-native/junixsocket-native.xcodeproj/project.pbxproj b/junixsocket-native/junixsocket-native.xcodeproj/project.pbxproj index a1bb6f52a..aa1edaa7f 100644 --- a/junixsocket-native/junixsocket-native.xcodeproj/project.pbxproj +++ b/junixsocket-native/junixsocket-native.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -13,6 +13,10 @@ buildPhases = ( ); dependencies = ( + 435B551B2A3F6658001011B8 /* PBXTargetDependency */, + 435B54DF2A3F6617001011B8 /* PBXTargetDependency */, + 435B54A32A3F65ED001011B8 /* PBXTargetDependency */, + 43EE6B472A3B3D0700E6F8F2 /* PBXTargetDependency */, 436B5AF32827E55000574CD4 /* PBXTargetDependency */, 436B5AB928244D5700574CD4 /* PBXTargetDependency */, 43B3E076266756780024822F /* PBXTargetDependency */, @@ -24,6 +28,7 @@ 432EF1E7261ADDF600F4A583 /* PBXTargetDependency */, 432EF1F6261ADEC000F4A583 /* PBXTargetDependency */, 432EF1D6261ADD9500F4A583 /* PBXTargetDependency */, + 430456D32C91B85A00CBAC42 /* PBXTargetDependency */, 43B3E0AE266971870024822F /* PBXTargetDependency */, 432EF18B261AD52800F4A583 /* PBXTargetDependency */, 43D0E25C261AE48B008F2FFE /* PBXTargetDependency */, @@ -544,6 +549,59 @@ 430235D72650AC3200823167 /* jniport.h in Headers */ = {isa = PBXBuildFile; fileRef = 430235C12650AC3200823167 /* jniport.h */; }; 430235D82650AC3200823167 /* jniport.h in Headers */ = {isa = PBXBuildFile; fileRef = 430235C12650AC3200823167 /* jniport.h */; }; 430235D92650AC3200823167 /* jniport.h in Headers */ = {isa = PBXBuildFile; fileRef = 430235C12650AC3200823167 /* jniport.h */; }; + 430456972C91B81000CBAC42 /* tipc.h in Headers */ = {isa = PBXBuildFile; fileRef = 436E165F27FBB70B00A553EC /* tipc.h */; }; + 430456982C91B81000CBAC42 /* jni.h in Headers */ = {isa = PBXBuildFile; fileRef = 430235C02650AC3200823167 /* jni.h */; }; + 430456992C91B81000CBAC42 /* ancillary.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233C52650AB4E00823167 /* ancillary.h */; }; + 4304569A2C91B81000CBAC42 /* pipe.h in Headers */ = {isa = PBXBuildFile; fileRef = 43B3DF81265EEC930024822F /* pipe.h */; }; + 4304569B2C91B81000CBAC42 /* init.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233BE2650AB4D00823167 /* init.h */; }; + 4304569C2C91B81000CBAC42 /* send.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AA2650AB4A00823167 /* send.h */; }; + 4304569D2C91B81000CBAC42 /* listen.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233B12650AB4B00823167 /* listen.h */; }; + 4304569E2C91B81000CBAC42 /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A22650AB4900823167 /* config.h */; }; + 4304569F2C91B81000CBAC42 /* socketpair.h in Headers */ = {isa = PBXBuildFile; fileRef = 43B3DFB6266082000024822F /* socketpair.h */; }; + 430456A02C91B81000CBAC42 /* vsock.h in Headers */ = {isa = PBXBuildFile; fileRef = 4355474828F6E6310042E830 /* vsock.h */; }; + 430456A12C91B81000CBAC42 /* connect.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A72650AB4A00823167 /* connect.h */; }; + 430456A22C91B81000CBAC42 /* jniutil.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A12650AB4900823167 /* jniutil.h */; }; + 430456A32C91B81000CBAC42 /* socketoptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 4302339D2650AB4900823167 /* socketoptions.h */; }; + 430456A42C91B81000CBAC42 /* filedescriptors.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AC2650AB4A00823167 /* filedescriptors.h */; }; + 430456A52C91B81000CBAC42 /* exceptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A82650AB4A00823167 /* exceptions.h */; }; + 430456A62C91B81000CBAC42 /* capabilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A42650AB4900823167 /* capabilities.h */; }; + 430456A72C91B81000CBAC42 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 430456A82C91B81000CBAC42 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233C02650AB4D00823167 /* org_newsclub_net_unix_NativeUnixSocket.h */; }; + 430456A92C91B81000CBAC42 /* socket.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233B42650AB4B00823167 /* socket.h */; }; + 430456AA2C91B81000CBAC42 /* ckmacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233BF2650AB4D00823167 /* ckmacros.h */; }; + 430456AB2C91B81000CBAC42 /* address.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233B92650AB4C00823167 /* address.h */; }; + 430456AC2C91B81000CBAC42 /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 430456AD2C91B81000CBAC42 /* polling.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A92650AB4A00823167 /* polling.h */; }; + 430456AE2C91B81000CBAC42 /* accept.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233BB2650AB4C00823167 /* accept.h */; }; + 430456AF2C91B81000CBAC42 /* reflection.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AD2650AB4A00823167 /* reflection.h */; }; + 430456B02C91B81000CBAC42 /* jniport.h in Headers */ = {isa = PBXBuildFile; fileRef = 430235C12650AC3200823167 /* jniport.h */; }; + 430456B12C91B81000CBAC42 /* receive.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A62650AB4A00823167 /* receive.h */; }; + 430456B22C91B81000CBAC42 /* credentials.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AE2650AB4A00823167 /* credentials.h */; }; + 430456B32C91B81000CBAC42 /* bind.h in Headers */ = {isa = PBXBuildFile; fileRef = 4302339F2650AB4900823167 /* bind.h */; }; + 430456B52C91B81000CBAC42 /* filedescriptors.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C42650AB4E00823167 /* filedescriptors.c */; }; + 430456B62C91B81000CBAC42 /* bind.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C32650AB4D00823167 /* bind.c */; }; + 430456B72C91B81000CBAC42 /* address.c in Sources */ = {isa = PBXBuildFile; fileRef = 4302339C2650AB4900823167 /* address.c */; }; + 430456B82C91B81000CBAC42 /* vsock.c in Sources */ = {isa = PBXBuildFile; fileRef = 4355474728F6E6310042E830 /* vsock.c */; }; + 430456B92C91B81000CBAC42 /* send.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B22650AB4B00823167 /* send.c */; }; + 430456BA2C91B81000CBAC42 /* socket.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C22650AB4D00823167 /* socket.c */; }; + 430456BB2C91B81000CBAC42 /* pipe.c in Sources */ = {isa = PBXBuildFile; fileRef = 43B3DF82265EEC930024822F /* pipe.c */; }; + 430456BC2C91B81000CBAC42 /* connect.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233BC2650AB4C00823167 /* connect.c */; }; + 430456BD2C91B81000CBAC42 /* capabilities.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B02650AB4B00823167 /* capabilities.c */; }; + 430456BE2C91B81000CBAC42 /* socketoptions.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B52650AB4B00823167 /* socketoptions.c */; }; + 430456BF2C91B81000CBAC42 /* polling.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C12650AB4D00823167 /* polling.c */; }; + 430456C02C91B81000CBAC42 /* receive.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233AB2650AB4A00823167 /* receive.c */; }; + 430456C12C91B81000CBAC42 /* jniutil.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233A32650AB4900823167 /* jniutil.c */; }; + 430456C22C91B81000CBAC42 /* init.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B32650AB4B00823167 /* init.c */; }; + 430456C32C91B81000CBAC42 /* tipc.c in Sources */ = {isa = PBXBuildFile; fileRef = 436E166027FBB70B00A553EC /* tipc.c */; }; + 430456C42C91B81000CBAC42 /* config.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233A02650AB4900823167 /* config.c */; }; + 430456C52C91B81000CBAC42 /* credentials.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C62650AB4E00823167 /* credentials.c */; }; + 430456C62C91B81000CBAC42 /* socketpair.c in Sources */ = {isa = PBXBuildFile; fileRef = 43B3DFB5266082000024822F /* socketpair.c */; }; + 430456C72C91B81000CBAC42 /* exceptions.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233BD2650AB4C00823167 /* exceptions.c */; }; + 430456C82C91B81000CBAC42 /* reflection.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233BA2650AB4C00823167 /* reflection.c */; }; + 430456C92C91B81000CBAC42 /* listen.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233AF2650AB4B00823167 /* listen.c */; }; + 430456CA2C91B81000CBAC42 /* ancillary.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233A52650AB4900823167 /* ancillary.c */; }; + 430456CB2C91B81000CBAC42 /* accept.c in Sources */ = {isa = PBXBuildFile; fileRef = 4302339E2650AB4900823167 /* accept.c */; }; + 430456CC2C91B81000CBAC42 /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; 4304A5F3266DFCEF003BCEDF /* jni.h in Headers */ = {isa = PBXBuildFile; fileRef = 430235C02650AC3200823167 /* jni.h */; }; 4304A5F4266DFCEF003BCEDF /* ancillary.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233C52650AB4E00823167 /* ancillary.h */; }; 4304A5F5266DFCEF003BCEDF /* pipe.h in Headers */ = {isa = PBXBuildFile; fileRef = 43B3DF81265EEC930024822F /* pipe.h */; }; @@ -590,6 +648,80 @@ 4304A61F266DFCEF003BCEDF /* listen.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233AF2650AB4B00823167 /* listen.c */; }; 4304A620266DFCEF003BCEDF /* ancillary.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233A52650AB4900823167 /* ancillary.c */; }; 4304A621266DFCEF003BCEDF /* accept.c in Sources */ = {isa = PBXBuildFile; fileRef = 4302339E2650AB4900823167 /* accept.c */; }; + 43145C4C2B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C4D2B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C4E2B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C4F2B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C502B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C512B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C522B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C532B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C542B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C552B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C562B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C572B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C582B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C592B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C5A2B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C5B2B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C5C2B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C5D2B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C5E2B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C5F2B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C602B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C612B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C622B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 43145C632B0656E30090BD53 /* gettod_zos.h in Headers */ = {isa = PBXBuildFile; fileRef = 43145C4B2B0656E30090BD53 /* gettod_zos.h */; }; + 434234192C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 4342341A2C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 4342341B2C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 4342341C2C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 4342341D2C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 4342341E2C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 4342341F2C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 434234202C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 434234212C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 434234222C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 434234232C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 434234242C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 434234252C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 434234262C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 434234272C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 434234282C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 434234292C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 4342342A2C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 4342342B2C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 4342342C2C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 4342342D2C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 4342342E2C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 4342342F2C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 434234302C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 434234312C91C2890046DBD9 /* stat_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 434234182C91C2890046DBD9 /* stat_wrapper.c */; }; + 434234342C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 434234352C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 434234362C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 434234372C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 434234382C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 434234392C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 4342343A2C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 4342343B2C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 4342343C2C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 4342343D2C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 4342343E2C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 4342343F2C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 434234402C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 434234412C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 434234422C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 434234432C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 434234442C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 434234452C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 434234462C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 434234472C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 434234482C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 434234492C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 4342344A2C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 4342344B2C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; + 4342344C2C91C4F50046DBD9 /* stat_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 434234332C91C4F50046DBD9 /* stat_wrapper.h */; }; 4355474928F6E6310042E830 /* vsock.c in Sources */ = {isa = PBXBuildFile; fileRef = 4355474728F6E6310042E830 /* vsock.c */; }; 4355474A28F6E6310042E830 /* vsock.c in Sources */ = {isa = PBXBuildFile; fileRef = 4355474728F6E6310042E830 /* vsock.c */; }; 4355474B28F6E6310042E830 /* vsock.c in Sources */ = {isa = PBXBuildFile; fileRef = 4355474728F6E6310042E830 /* vsock.c */; }; @@ -630,6 +762,156 @@ 4355476E28F6E6310042E830 /* vsock.h in Headers */ = {isa = PBXBuildFile; fileRef = 4355474828F6E6310042E830 /* vsock.h */; }; 4355476F28F6E6310042E830 /* vsock.h in Headers */ = {isa = PBXBuildFile; fileRef = 4355474828F6E6310042E830 /* vsock.h */; }; 4355477028F6E6310042E830 /* vsock.h in Headers */ = {isa = PBXBuildFile; fileRef = 4355474828F6E6310042E830 /* vsock.h */; }; + 435B546A2A3F65D2001011B8 /* tipc.h in Headers */ = {isa = PBXBuildFile; fileRef = 436E165F27FBB70B00A553EC /* tipc.h */; }; + 435B546B2A3F65D2001011B8 /* jni.h in Headers */ = {isa = PBXBuildFile; fileRef = 430235C02650AC3200823167 /* jni.h */; }; + 435B546C2A3F65D2001011B8 /* ancillary.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233C52650AB4E00823167 /* ancillary.h */; }; + 435B546D2A3F65D2001011B8 /* pipe.h in Headers */ = {isa = PBXBuildFile; fileRef = 43B3DF81265EEC930024822F /* pipe.h */; }; + 435B546E2A3F65D2001011B8 /* init.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233BE2650AB4D00823167 /* init.h */; }; + 435B546F2A3F65D2001011B8 /* send.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AA2650AB4A00823167 /* send.h */; }; + 435B54702A3F65D2001011B8 /* listen.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233B12650AB4B00823167 /* listen.h */; }; + 435B54712A3F65D2001011B8 /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A22650AB4900823167 /* config.h */; }; + 435B54722A3F65D2001011B8 /* socketpair.h in Headers */ = {isa = PBXBuildFile; fileRef = 43B3DFB6266082000024822F /* socketpair.h */; }; + 435B54732A3F65D2001011B8 /* vsock.h in Headers */ = {isa = PBXBuildFile; fileRef = 4355474828F6E6310042E830 /* vsock.h */; }; + 435B54742A3F65D2001011B8 /* connect.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A72650AB4A00823167 /* connect.h */; }; + 435B54752A3F65D2001011B8 /* jniutil.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A12650AB4900823167 /* jniutil.h */; }; + 435B54762A3F65D2001011B8 /* socketoptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 4302339D2650AB4900823167 /* socketoptions.h */; }; + 435B54772A3F65D2001011B8 /* filedescriptors.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AC2650AB4A00823167 /* filedescriptors.h */; }; + 435B54782A3F65D2001011B8 /* exceptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A82650AB4A00823167 /* exceptions.h */; }; + 435B54792A3F65D2001011B8 /* capabilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A42650AB4900823167 /* capabilities.h */; }; + 435B547A2A3F65D2001011B8 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233C02650AB4D00823167 /* org_newsclub_net_unix_NativeUnixSocket.h */; }; + 435B547B2A3F65D2001011B8 /* socket.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233B42650AB4B00823167 /* socket.h */; }; + 435B547C2A3F65D2001011B8 /* ckmacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233BF2650AB4D00823167 /* ckmacros.h */; }; + 435B547D2A3F65D2001011B8 /* address.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233B92650AB4C00823167 /* address.h */; }; + 435B547E2A3F65D2001011B8 /* polling.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A92650AB4A00823167 /* polling.h */; }; + 435B547F2A3F65D2001011B8 /* accept.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233BB2650AB4C00823167 /* accept.h */; }; + 435B54802A3F65D2001011B8 /* reflection.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AD2650AB4A00823167 /* reflection.h */; }; + 435B54812A3F65D2001011B8 /* jniport.h in Headers */ = {isa = PBXBuildFile; fileRef = 430235C12650AC3200823167 /* jniport.h */; }; + 435B54822A3F65D2001011B8 /* receive.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A62650AB4A00823167 /* receive.h */; }; + 435B54832A3F65D2001011B8 /* credentials.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AE2650AB4A00823167 /* credentials.h */; }; + 435B54842A3F65D2001011B8 /* bind.h in Headers */ = {isa = PBXBuildFile; fileRef = 4302339F2650AB4900823167 /* bind.h */; }; + 435B54862A3F65D2001011B8 /* filedescriptors.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C42650AB4E00823167 /* filedescriptors.c */; }; + 435B54872A3F65D2001011B8 /* bind.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C32650AB4D00823167 /* bind.c */; }; + 435B54882A3F65D2001011B8 /* address.c in Sources */ = {isa = PBXBuildFile; fileRef = 4302339C2650AB4900823167 /* address.c */; }; + 435B54892A3F65D2001011B8 /* vsock.c in Sources */ = {isa = PBXBuildFile; fileRef = 4355474728F6E6310042E830 /* vsock.c */; }; + 435B548A2A3F65D2001011B8 /* send.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B22650AB4B00823167 /* send.c */; }; + 435B548B2A3F65D2001011B8 /* socket.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C22650AB4D00823167 /* socket.c */; }; + 435B548C2A3F65D2001011B8 /* pipe.c in Sources */ = {isa = PBXBuildFile; fileRef = 43B3DF82265EEC930024822F /* pipe.c */; }; + 435B548D2A3F65D2001011B8 /* connect.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233BC2650AB4C00823167 /* connect.c */; }; + 435B548E2A3F65D2001011B8 /* capabilities.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B02650AB4B00823167 /* capabilities.c */; }; + 435B548F2A3F65D2001011B8 /* socketoptions.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B52650AB4B00823167 /* socketoptions.c */; }; + 435B54902A3F65D2001011B8 /* polling.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C12650AB4D00823167 /* polling.c */; }; + 435B54912A3F65D2001011B8 /* receive.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233AB2650AB4A00823167 /* receive.c */; }; + 435B54922A3F65D2001011B8 /* jniutil.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233A32650AB4900823167 /* jniutil.c */; }; + 435B54932A3F65D2001011B8 /* init.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B32650AB4B00823167 /* init.c */; }; + 435B54942A3F65D2001011B8 /* tipc.c in Sources */ = {isa = PBXBuildFile; fileRef = 436E166027FBB70B00A553EC /* tipc.c */; }; + 435B54952A3F65D2001011B8 /* config.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233A02650AB4900823167 /* config.c */; }; + 435B54962A3F65D2001011B8 /* credentials.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C62650AB4E00823167 /* credentials.c */; }; + 435B54972A3F65D2001011B8 /* socketpair.c in Sources */ = {isa = PBXBuildFile; fileRef = 43B3DFB5266082000024822F /* socketpair.c */; }; + 435B54982A3F65D2001011B8 /* exceptions.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233BD2650AB4C00823167 /* exceptions.c */; }; + 435B54992A3F65D2001011B8 /* reflection.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233BA2650AB4C00823167 /* reflection.c */; }; + 435B549A2A3F65D2001011B8 /* listen.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233AF2650AB4B00823167 /* listen.c */; }; + 435B549B2A3F65D2001011B8 /* ancillary.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233A52650AB4900823167 /* ancillary.c */; }; + 435B549C2A3F65D2001011B8 /* accept.c in Sources */ = {isa = PBXBuildFile; fileRef = 4302339E2650AB4900823167 /* accept.c */; }; + 435B54A62A3F65F4001011B8 /* tipc.h in Headers */ = {isa = PBXBuildFile; fileRef = 436E165F27FBB70B00A553EC /* tipc.h */; }; + 435B54A72A3F65F4001011B8 /* jni.h in Headers */ = {isa = PBXBuildFile; fileRef = 430235C02650AC3200823167 /* jni.h */; }; + 435B54A82A3F65F4001011B8 /* ancillary.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233C52650AB4E00823167 /* ancillary.h */; }; + 435B54A92A3F65F4001011B8 /* pipe.h in Headers */ = {isa = PBXBuildFile; fileRef = 43B3DF81265EEC930024822F /* pipe.h */; }; + 435B54AA2A3F65F4001011B8 /* init.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233BE2650AB4D00823167 /* init.h */; }; + 435B54AB2A3F65F4001011B8 /* send.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AA2650AB4A00823167 /* send.h */; }; + 435B54AC2A3F65F4001011B8 /* listen.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233B12650AB4B00823167 /* listen.h */; }; + 435B54AD2A3F65F4001011B8 /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A22650AB4900823167 /* config.h */; }; + 435B54AE2A3F65F4001011B8 /* socketpair.h in Headers */ = {isa = PBXBuildFile; fileRef = 43B3DFB6266082000024822F /* socketpair.h */; }; + 435B54AF2A3F65F4001011B8 /* vsock.h in Headers */ = {isa = PBXBuildFile; fileRef = 4355474828F6E6310042E830 /* vsock.h */; }; + 435B54B02A3F65F4001011B8 /* connect.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A72650AB4A00823167 /* connect.h */; }; + 435B54B12A3F65F4001011B8 /* jniutil.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A12650AB4900823167 /* jniutil.h */; }; + 435B54B22A3F65F4001011B8 /* socketoptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 4302339D2650AB4900823167 /* socketoptions.h */; }; + 435B54B32A3F65F4001011B8 /* filedescriptors.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AC2650AB4A00823167 /* filedescriptors.h */; }; + 435B54B42A3F65F4001011B8 /* exceptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A82650AB4A00823167 /* exceptions.h */; }; + 435B54B52A3F65F4001011B8 /* capabilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A42650AB4900823167 /* capabilities.h */; }; + 435B54B62A3F65F4001011B8 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233C02650AB4D00823167 /* org_newsclub_net_unix_NativeUnixSocket.h */; }; + 435B54B72A3F65F4001011B8 /* socket.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233B42650AB4B00823167 /* socket.h */; }; + 435B54B82A3F65F4001011B8 /* ckmacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233BF2650AB4D00823167 /* ckmacros.h */; }; + 435B54B92A3F65F4001011B8 /* address.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233B92650AB4C00823167 /* address.h */; }; + 435B54BA2A3F65F4001011B8 /* polling.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A92650AB4A00823167 /* polling.h */; }; + 435B54BB2A3F65F4001011B8 /* accept.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233BB2650AB4C00823167 /* accept.h */; }; + 435B54BC2A3F65F4001011B8 /* reflection.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AD2650AB4A00823167 /* reflection.h */; }; + 435B54BD2A3F65F4001011B8 /* jniport.h in Headers */ = {isa = PBXBuildFile; fileRef = 430235C12650AC3200823167 /* jniport.h */; }; + 435B54BE2A3F65F4001011B8 /* receive.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A62650AB4A00823167 /* receive.h */; }; + 435B54BF2A3F65F4001011B8 /* credentials.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AE2650AB4A00823167 /* credentials.h */; }; + 435B54C02A3F65F4001011B8 /* bind.h in Headers */ = {isa = PBXBuildFile; fileRef = 4302339F2650AB4900823167 /* bind.h */; }; + 435B54C22A3F65F4001011B8 /* filedescriptors.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C42650AB4E00823167 /* filedescriptors.c */; }; + 435B54C32A3F65F4001011B8 /* bind.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C32650AB4D00823167 /* bind.c */; }; + 435B54C42A3F65F4001011B8 /* address.c in Sources */ = {isa = PBXBuildFile; fileRef = 4302339C2650AB4900823167 /* address.c */; }; + 435B54C52A3F65F4001011B8 /* vsock.c in Sources */ = {isa = PBXBuildFile; fileRef = 4355474728F6E6310042E830 /* vsock.c */; }; + 435B54C62A3F65F4001011B8 /* send.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B22650AB4B00823167 /* send.c */; }; + 435B54C72A3F65F4001011B8 /* socket.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C22650AB4D00823167 /* socket.c */; }; + 435B54C82A3F65F4001011B8 /* pipe.c in Sources */ = {isa = PBXBuildFile; fileRef = 43B3DF82265EEC930024822F /* pipe.c */; }; + 435B54C92A3F65F4001011B8 /* connect.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233BC2650AB4C00823167 /* connect.c */; }; + 435B54CA2A3F65F4001011B8 /* capabilities.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B02650AB4B00823167 /* capabilities.c */; }; + 435B54CB2A3F65F4001011B8 /* socketoptions.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B52650AB4B00823167 /* socketoptions.c */; }; + 435B54CC2A3F65F4001011B8 /* polling.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C12650AB4D00823167 /* polling.c */; }; + 435B54CD2A3F65F4001011B8 /* receive.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233AB2650AB4A00823167 /* receive.c */; }; + 435B54CE2A3F65F4001011B8 /* jniutil.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233A32650AB4900823167 /* jniutil.c */; }; + 435B54CF2A3F65F4001011B8 /* init.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B32650AB4B00823167 /* init.c */; }; + 435B54D02A3F65F4001011B8 /* tipc.c in Sources */ = {isa = PBXBuildFile; fileRef = 436E166027FBB70B00A553EC /* tipc.c */; }; + 435B54D12A3F65F4001011B8 /* config.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233A02650AB4900823167 /* config.c */; }; + 435B54D22A3F65F4001011B8 /* credentials.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C62650AB4E00823167 /* credentials.c */; }; + 435B54D32A3F65F4001011B8 /* socketpair.c in Sources */ = {isa = PBXBuildFile; fileRef = 43B3DFB5266082000024822F /* socketpair.c */; }; + 435B54D42A3F65F4001011B8 /* exceptions.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233BD2650AB4C00823167 /* exceptions.c */; }; + 435B54D52A3F65F4001011B8 /* reflection.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233BA2650AB4C00823167 /* reflection.c */; }; + 435B54D62A3F65F4001011B8 /* listen.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233AF2650AB4B00823167 /* listen.c */; }; + 435B54D72A3F65F4001011B8 /* ancillary.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233A52650AB4900823167 /* ancillary.c */; }; + 435B54D82A3F65F4001011B8 /* accept.c in Sources */ = {isa = PBXBuildFile; fileRef = 4302339E2650AB4900823167 /* accept.c */; }; + 435B54E22A3F661F001011B8 /* tipc.h in Headers */ = {isa = PBXBuildFile; fileRef = 436E165F27FBB70B00A553EC /* tipc.h */; }; + 435B54E32A3F661F001011B8 /* jni.h in Headers */ = {isa = PBXBuildFile; fileRef = 430235C02650AC3200823167 /* jni.h */; }; + 435B54E42A3F661F001011B8 /* ancillary.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233C52650AB4E00823167 /* ancillary.h */; }; + 435B54E52A3F661F001011B8 /* pipe.h in Headers */ = {isa = PBXBuildFile; fileRef = 43B3DF81265EEC930024822F /* pipe.h */; }; + 435B54E62A3F661F001011B8 /* init.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233BE2650AB4D00823167 /* init.h */; }; + 435B54E72A3F661F001011B8 /* send.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AA2650AB4A00823167 /* send.h */; }; + 435B54E82A3F661F001011B8 /* listen.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233B12650AB4B00823167 /* listen.h */; }; + 435B54E92A3F661F001011B8 /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A22650AB4900823167 /* config.h */; }; + 435B54EA2A3F661F001011B8 /* socketpair.h in Headers */ = {isa = PBXBuildFile; fileRef = 43B3DFB6266082000024822F /* socketpair.h */; }; + 435B54EB2A3F661F001011B8 /* vsock.h in Headers */ = {isa = PBXBuildFile; fileRef = 4355474828F6E6310042E830 /* vsock.h */; }; + 435B54EC2A3F661F001011B8 /* connect.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A72650AB4A00823167 /* connect.h */; }; + 435B54ED2A3F661F001011B8 /* jniutil.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A12650AB4900823167 /* jniutil.h */; }; + 435B54EE2A3F661F001011B8 /* socketoptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 4302339D2650AB4900823167 /* socketoptions.h */; }; + 435B54EF2A3F661F001011B8 /* filedescriptors.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AC2650AB4A00823167 /* filedescriptors.h */; }; + 435B54F02A3F661F001011B8 /* exceptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A82650AB4A00823167 /* exceptions.h */; }; + 435B54F12A3F661F001011B8 /* capabilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A42650AB4900823167 /* capabilities.h */; }; + 435B54F22A3F661F001011B8 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233C02650AB4D00823167 /* org_newsclub_net_unix_NativeUnixSocket.h */; }; + 435B54F32A3F661F001011B8 /* socket.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233B42650AB4B00823167 /* socket.h */; }; + 435B54F42A3F661F001011B8 /* ckmacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233BF2650AB4D00823167 /* ckmacros.h */; }; + 435B54F52A3F661F001011B8 /* address.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233B92650AB4C00823167 /* address.h */; }; + 435B54F62A3F661F001011B8 /* polling.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A92650AB4A00823167 /* polling.h */; }; + 435B54F72A3F661F001011B8 /* accept.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233BB2650AB4C00823167 /* accept.h */; }; + 435B54F82A3F661F001011B8 /* reflection.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AD2650AB4A00823167 /* reflection.h */; }; + 435B54F92A3F661F001011B8 /* jniport.h in Headers */ = {isa = PBXBuildFile; fileRef = 430235C12650AC3200823167 /* jniport.h */; }; + 435B54FA2A3F661F001011B8 /* receive.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A62650AB4A00823167 /* receive.h */; }; + 435B54FB2A3F661F001011B8 /* credentials.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AE2650AB4A00823167 /* credentials.h */; }; + 435B54FC2A3F661F001011B8 /* bind.h in Headers */ = {isa = PBXBuildFile; fileRef = 4302339F2650AB4900823167 /* bind.h */; }; + 435B54FE2A3F661F001011B8 /* filedescriptors.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C42650AB4E00823167 /* filedescriptors.c */; }; + 435B54FF2A3F661F001011B8 /* bind.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C32650AB4D00823167 /* bind.c */; }; + 435B55002A3F661F001011B8 /* address.c in Sources */ = {isa = PBXBuildFile; fileRef = 4302339C2650AB4900823167 /* address.c */; }; + 435B55012A3F661F001011B8 /* vsock.c in Sources */ = {isa = PBXBuildFile; fileRef = 4355474728F6E6310042E830 /* vsock.c */; }; + 435B55022A3F661F001011B8 /* send.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B22650AB4B00823167 /* send.c */; }; + 435B55032A3F661F001011B8 /* socket.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C22650AB4D00823167 /* socket.c */; }; + 435B55042A3F661F001011B8 /* pipe.c in Sources */ = {isa = PBXBuildFile; fileRef = 43B3DF82265EEC930024822F /* pipe.c */; }; + 435B55052A3F661F001011B8 /* connect.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233BC2650AB4C00823167 /* connect.c */; }; + 435B55062A3F661F001011B8 /* capabilities.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B02650AB4B00823167 /* capabilities.c */; }; + 435B55072A3F661F001011B8 /* socketoptions.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B52650AB4B00823167 /* socketoptions.c */; }; + 435B55082A3F661F001011B8 /* polling.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C12650AB4D00823167 /* polling.c */; }; + 435B55092A3F661F001011B8 /* receive.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233AB2650AB4A00823167 /* receive.c */; }; + 435B550A2A3F661F001011B8 /* jniutil.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233A32650AB4900823167 /* jniutil.c */; }; + 435B550B2A3F661F001011B8 /* init.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B32650AB4B00823167 /* init.c */; }; + 435B550C2A3F661F001011B8 /* tipc.c in Sources */ = {isa = PBXBuildFile; fileRef = 436E166027FBB70B00A553EC /* tipc.c */; }; + 435B550D2A3F661F001011B8 /* config.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233A02650AB4900823167 /* config.c */; }; + 435B550E2A3F661F001011B8 /* credentials.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C62650AB4E00823167 /* credentials.c */; }; + 435B550F2A3F661F001011B8 /* socketpair.c in Sources */ = {isa = PBXBuildFile; fileRef = 43B3DFB5266082000024822F /* socketpair.c */; }; + 435B55102A3F661F001011B8 /* exceptions.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233BD2650AB4C00823167 /* exceptions.c */; }; + 435B55112A3F661F001011B8 /* reflection.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233BA2650AB4C00823167 /* reflection.c */; }; + 435B55122A3F661F001011B8 /* listen.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233AF2650AB4B00823167 /* listen.c */; }; + 435B55132A3F661F001011B8 /* ancillary.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233A52650AB4900823167 /* ancillary.c */; }; + 435B55142A3F661F001011B8 /* accept.c in Sources */ = {isa = PBXBuildFile; fileRef = 4302339E2650AB4900823167 /* accept.c */; }; 43684D82282EE24B005F2989 /* tipc.h in Headers */ = {isa = PBXBuildFile; fileRef = 436E165F27FBB70B00A553EC /* tipc.h */; }; 43684D83282EE24B005F2989 /* jni.h in Headers */ = {isa = PBXBuildFile; fileRef = 430235C02650AC3200823167 /* jni.h */; }; 43684D84282EE24B005F2989 /* ancillary.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233C52650AB4E00823167 /* ancillary.h */; }; @@ -808,6 +1090,54 @@ 436E168027FBB70B00A553EC /* tipc.c in Sources */ = {isa = PBXBuildFile; fileRef = 436E166027FBB70B00A553EC /* tipc.c */; }; 436E168127FBB70B00A553EC /* tipc.c in Sources */ = {isa = PBXBuildFile; fileRef = 436E166027FBB70B00A553EC /* tipc.c */; }; 436E168227FBB70B00A553EC /* tipc.c in Sources */ = {isa = PBXBuildFile; fileRef = 436E166027FBB70B00A553EC /* tipc.c */; }; + 438F4D972A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4D982A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4D992A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4D9A2A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4D9B2A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4D9C2A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4D9D2A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4D9E2A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4D9F2A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4DA02A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4DA12A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4DA22A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4DA32A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4DA42A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4DA52A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4DA62A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4DA72A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4DA82A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4DA92A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4DAA2A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4DAB2A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4DAC2A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4DAD2A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4DAE2A6AF05000546E2C /* afsystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 438F4D952A6AF05000546E2C /* afsystem.c */; }; + 438F4DAF2A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DB02A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DB12A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DB22A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DB32A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DB42A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DB52A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DB62A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DB72A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DB82A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DB92A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DBA2A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DBB2A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DBC2A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DBD2A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DBE2A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DBF2A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DC02A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DC12A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DC22A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DC32A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DC42A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DC52A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; + 438F4DC62A6AF05100546E2C /* afsystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 438F4D962A6AF05000546E2C /* afsystem.h */; }; 43B3DF83265EEC930024822F /* pipe.h in Headers */ = {isa = PBXBuildFile; fileRef = 43B3DF81265EEC930024822F /* pipe.h */; }; 43B3DF84265EEC930024822F /* pipe.h in Headers */ = {isa = PBXBuildFile; fileRef = 43B3DF81265EEC930024822F /* pipe.h */; }; 43B3DF85265EEC930024822F /* pipe.h in Headers */ = {isa = PBXBuildFile; fileRef = 43B3DF81265EEC930024822F /* pipe.h */; }; @@ -1040,6 +1370,56 @@ 43B3E0A5266971630024822F /* listen.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233AF2650AB4B00823167 /* listen.c */; }; 43B3E0A6266971630024822F /* ancillary.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233A52650AB4900823167 /* ancillary.c */; }; 43B3E0A7266971630024822F /* accept.c in Sources */ = {isa = PBXBuildFile; fileRef = 4302339E2650AB4900823167 /* accept.c */; }; + 43C57CC72A38F370001DEA62 /* tipc.h in Headers */ = {isa = PBXBuildFile; fileRef = 436E165F27FBB70B00A553EC /* tipc.h */; }; + 43C57CC82A38F370001DEA62 /* jni.h in Headers */ = {isa = PBXBuildFile; fileRef = 430235C02650AC3200823167 /* jni.h */; }; + 43C57CC92A38F370001DEA62 /* ancillary.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233C52650AB4E00823167 /* ancillary.h */; }; + 43C57CCA2A38F370001DEA62 /* pipe.h in Headers */ = {isa = PBXBuildFile; fileRef = 43B3DF81265EEC930024822F /* pipe.h */; }; + 43C57CCB2A38F370001DEA62 /* init.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233BE2650AB4D00823167 /* init.h */; }; + 43C57CCC2A38F370001DEA62 /* send.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AA2650AB4A00823167 /* send.h */; }; + 43C57CCD2A38F370001DEA62 /* listen.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233B12650AB4B00823167 /* listen.h */; }; + 43C57CCE2A38F370001DEA62 /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A22650AB4900823167 /* config.h */; }; + 43C57CCF2A38F370001DEA62 /* socketpair.h in Headers */ = {isa = PBXBuildFile; fileRef = 43B3DFB6266082000024822F /* socketpair.h */; }; + 43C57CD02A38F370001DEA62 /* vsock.h in Headers */ = {isa = PBXBuildFile; fileRef = 4355474828F6E6310042E830 /* vsock.h */; }; + 43C57CD12A38F370001DEA62 /* connect.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A72650AB4A00823167 /* connect.h */; }; + 43C57CD22A38F370001DEA62 /* jniutil.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A12650AB4900823167 /* jniutil.h */; }; + 43C57CD32A38F370001DEA62 /* socketoptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 4302339D2650AB4900823167 /* socketoptions.h */; }; + 43C57CD42A38F370001DEA62 /* filedescriptors.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AC2650AB4A00823167 /* filedescriptors.h */; }; + 43C57CD52A38F370001DEA62 /* exceptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A82650AB4A00823167 /* exceptions.h */; }; + 43C57CD62A38F370001DEA62 /* capabilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A42650AB4900823167 /* capabilities.h */; }; + 43C57CD72A38F370001DEA62 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233C02650AB4D00823167 /* org_newsclub_net_unix_NativeUnixSocket.h */; }; + 43C57CD82A38F370001DEA62 /* socket.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233B42650AB4B00823167 /* socket.h */; }; + 43C57CD92A38F370001DEA62 /* ckmacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233BF2650AB4D00823167 /* ckmacros.h */; }; + 43C57CDA2A38F370001DEA62 /* address.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233B92650AB4C00823167 /* address.h */; }; + 43C57CDB2A38F370001DEA62 /* polling.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A92650AB4A00823167 /* polling.h */; }; + 43C57CDC2A38F370001DEA62 /* accept.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233BB2650AB4C00823167 /* accept.h */; }; + 43C57CDD2A38F370001DEA62 /* reflection.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AD2650AB4A00823167 /* reflection.h */; }; + 43C57CDE2A38F370001DEA62 /* jniport.h in Headers */ = {isa = PBXBuildFile; fileRef = 430235C12650AC3200823167 /* jniport.h */; }; + 43C57CDF2A38F370001DEA62 /* receive.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233A62650AB4A00823167 /* receive.h */; }; + 43C57CE02A38F370001DEA62 /* credentials.h in Headers */ = {isa = PBXBuildFile; fileRef = 430233AE2650AB4A00823167 /* credentials.h */; }; + 43C57CE12A38F370001DEA62 /* bind.h in Headers */ = {isa = PBXBuildFile; fileRef = 4302339F2650AB4900823167 /* bind.h */; }; + 43C57CE32A38F370001DEA62 /* filedescriptors.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C42650AB4E00823167 /* filedescriptors.c */; }; + 43C57CE42A38F370001DEA62 /* bind.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C32650AB4D00823167 /* bind.c */; }; + 43C57CE52A38F370001DEA62 /* address.c in Sources */ = {isa = PBXBuildFile; fileRef = 4302339C2650AB4900823167 /* address.c */; }; + 43C57CE62A38F370001DEA62 /* vsock.c in Sources */ = {isa = PBXBuildFile; fileRef = 4355474728F6E6310042E830 /* vsock.c */; }; + 43C57CE72A38F370001DEA62 /* send.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B22650AB4B00823167 /* send.c */; }; + 43C57CE82A38F370001DEA62 /* socket.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C22650AB4D00823167 /* socket.c */; }; + 43C57CE92A38F370001DEA62 /* pipe.c in Sources */ = {isa = PBXBuildFile; fileRef = 43B3DF82265EEC930024822F /* pipe.c */; }; + 43C57CEA2A38F370001DEA62 /* connect.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233BC2650AB4C00823167 /* connect.c */; }; + 43C57CEB2A38F370001DEA62 /* capabilities.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B02650AB4B00823167 /* capabilities.c */; }; + 43C57CEC2A38F370001DEA62 /* socketoptions.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B52650AB4B00823167 /* socketoptions.c */; }; + 43C57CED2A38F370001DEA62 /* polling.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C12650AB4D00823167 /* polling.c */; }; + 43C57CEE2A38F370001DEA62 /* receive.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233AB2650AB4A00823167 /* receive.c */; }; + 43C57CEF2A38F370001DEA62 /* jniutil.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233A32650AB4900823167 /* jniutil.c */; }; + 43C57CF02A38F370001DEA62 /* init.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233B32650AB4B00823167 /* init.c */; }; + 43C57CF12A38F370001DEA62 /* tipc.c in Sources */ = {isa = PBXBuildFile; fileRef = 436E166027FBB70B00A553EC /* tipc.c */; }; + 43C57CF22A38F370001DEA62 /* config.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233A02650AB4900823167 /* config.c */; }; + 43C57CF32A38F370001DEA62 /* credentials.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233C62650AB4E00823167 /* credentials.c */; }; + 43C57CF42A38F370001DEA62 /* socketpair.c in Sources */ = {isa = PBXBuildFile; fileRef = 43B3DFB5266082000024822F /* socketpair.c */; }; + 43C57CF52A38F370001DEA62 /* exceptions.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233BD2650AB4C00823167 /* exceptions.c */; }; + 43C57CF62A38F370001DEA62 /* reflection.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233BA2650AB4C00823167 /* reflection.c */; }; + 43C57CF72A38F370001DEA62 /* listen.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233AF2650AB4B00823167 /* listen.c */; }; + 43C57CF82A38F370001DEA62 /* ancillary.c in Sources */ = {isa = PBXBuildFile; fileRef = 430233A52650AB4900823167 /* ancillary.c */; }; + 43C57CF92A38F370001DEA62 /* accept.c in Sources */ = {isa = PBXBuildFile; fileRef = 4302339E2650AB4900823167 /* accept.c */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1050,6 +1430,13 @@ remoteGlobalIDString = 43663ED6261AECC400CEED08; remoteInfo = default; }; + 430456D22C91B85A00CBAC42 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 43D436B0261A55D0002D0C12 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 430456952C91B81000CBAC42; + remoteInfo = "loongarch64-openEuler-linux"; + }; 4304A627266DFD24003BCEDF /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 43D436B0261A55D0002D0C12 /* Project object */; @@ -1127,6 +1514,27 @@ remoteGlobalIDString = 432EF1F7261ADEDD00F4A583; remoteInfo = "armv6--linux-gnueabihf"; }; + 435B54A22A3F65ED001011B8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 43D436B0261A55D0002D0C12 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 435B54682A3F65D2001011B8; + remoteInfo = "x86_64-linux-android30"; + }; + 435B54DE2A3F6617001011B8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 43D436B0261A55D0002D0C12 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 435B54A42A3F65F4001011B8; + remoteInfo = "i686-linux-android30"; + }; + 435B551A2A3F6658001011B8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 43D436B0261A55D0002D0C12 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 435B54E02A3F661F001011B8; + remoteInfo = "arm-linux-androideabi30"; + }; 43684DB8282EE2CA005F2989 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 43D436B0261A55D0002D0C12 /* Project object */; @@ -1183,6 +1591,13 @@ remoteGlobalIDString = 432EF206261AE29200F4A583; remoteInfo = "x86_64-apple-darwin18.2.0"; }; + 43EE6B462A3B3D0700E6F8F2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 43D436B0261A55D0002D0C12 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43C57CC52A38F370001DEA62; + remoteInfo = "aarch64-linux-android30"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -1228,7 +1643,9 @@ 430233C62650AB4E00823167 /* credentials.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = credentials.c; path = src/main/c/credentials.c; sourceTree = ""; }; 430235C02650AC3200823167 /* jni.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jni.h; sourceTree = ""; }; 430235C12650AC3200823167 /* jniport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jniport.h; sourceTree = ""; }; + 430456D12C91B81000CBAC42 /* libjunixsocket-loongarch64-openEuler-linux.so */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-loongarch64-openEuler-linux.so"; sourceTree = BUILT_PRODUCTS_DIR; }; 4304A626266DFCEF003BCEDF /* libjunixsocket-x86_64-portbld-dragonfly6.0.so */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-x86_64-portbld-dragonfly6.0.so"; sourceTree = BUILT_PRODUCTS_DIR; }; + 43145C4B2B0656E30090BD53 /* gettod_zos.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gettod_zos.h; path = src/main/c/gettod_zos.h; sourceTree = ""; }; 432EF19A261AD60200F4A583 /* libjunixsocket-aarch64-linux-gnu.so */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-aarch64-linux-gnu.so"; sourceTree = BUILT_PRODUCTS_DIR; }; 432EF1A9261AD6D700F4A583 /* libjunixsocket-x86_64-w64-mingw32.so */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-x86_64-w64-mingw32.so"; sourceTree = BUILT_PRODUCTS_DIR; }; 432EF1B8261AD9FB00F4A583 /* libjunixsocket-i386-pc-solaris2.11_m64.so */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-i386-pc-solaris2.11_m64.so"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1238,14 +1655,21 @@ 432EF1F4261ADE4900F4A583 /* libjunixsocket-riscv64-redhat-linux.so */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-riscv64-redhat-linux.so"; sourceTree = BUILT_PRODUCTS_DIR; }; 432EF203261ADEDD00F4A583 /* libjunixsocket-armv6--linux-gnueabihf.so */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-armv6--linux-gnueabihf.so"; sourceTree = BUILT_PRODUCTS_DIR; }; 432EF212261AE29200F4A583 /* libjunixsocket-x86_64-apple-darwin18.2.0.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-x86_64-apple-darwin18.2.0.dylib"; sourceTree = BUILT_PRODUCTS_DIR; }; + 434234182C91C2890046DBD9 /* stat_wrapper.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = stat_wrapper.c; path = src/main/c/stat_wrapper.c; sourceTree = ""; }; + 434234332C91C4F50046DBD9 /* stat_wrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = stat_wrapper.h; path = src/main/c/stat_wrapper.h; sourceTree = ""; }; 4355474728F6E6310042E830 /* vsock.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = vsock.c; path = src/main/c/vsock.c; sourceTree = ""; }; 4355474828F6E6310042E830 /* vsock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vsock.h; path = src/main/c/vsock.h; sourceTree = ""; }; + 435B54A12A3F65D2001011B8 /* libjunixsocket-x86_64-linux-android30.so */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-x86_64-linux-android30.so"; sourceTree = BUILT_PRODUCTS_DIR; }; + 435B54DD2A3F65F4001011B8 /* libjunixsocket-i686-linux-android30.so */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-i686-linux-android30.so"; sourceTree = BUILT_PRODUCTS_DIR; }; + 435B55192A3F661F001011B8 /* libjunixsocket-arm-linux-androideabi30.so */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-arm-linux-androideabi30.so"; sourceTree = BUILT_PRODUCTS_DIR; }; 43663EE2261AECC400CEED08 /* libjunixsocket-current.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-current.dylib"; sourceTree = BUILT_PRODUCTS_DIR; }; 43684DB7282EE24B005F2989 /* libjunixsocket-aarch64-w64-mingw32.so */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-aarch64-w64-mingw32.so"; sourceTree = BUILT_PRODUCTS_DIR; }; 436B5AB728244CB500574CD4 /* libjunixsocket-powerpc-ibm-aix7.2.so */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-powerpc-ibm-aix7.2.so"; sourceTree = BUILT_PRODUCTS_DIR; }; 436B5AF12826F49A00574CD4 /* libjunixsocket-powerpc-ibm-os400.so */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-powerpc-ibm-os400.so"; sourceTree = BUILT_PRODUCTS_DIR; }; 436E165F27FBB70B00A553EC /* tipc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = tipc.h; path = src/main/c/tipc.h; sourceTree = ""; }; 436E166027FBB70B00A553EC /* tipc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = tipc.c; path = src/main/c/tipc.c; sourceTree = ""; }; + 438F4D952A6AF05000546E2C /* afsystem.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = afsystem.c; path = src/main/c/afsystem.c; sourceTree = ""; }; + 438F4D962A6AF05000546E2C /* afsystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = afsystem.h; path = src/main/c/afsystem.h; sourceTree = ""; }; 43B3DF81265EEC930024822F /* pipe.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = pipe.h; path = src/main/c/pipe.h; sourceTree = ""; }; 43B3DF82265EEC930024822F /* pipe.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = pipe.c; path = src/main/c/pipe.c; sourceTree = ""; }; 43B3DFB5266082000024822F /* socketpair.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = socketpair.c; path = src/main/c/socketpair.c; sourceTree = ""; }; @@ -1254,11 +1678,19 @@ 43B3E03C26672FD30024822F /* libjunixsocket-x86_64-unknown-linux-gnu.so */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-x86_64-unknown-linux-gnu.so"; sourceTree = BUILT_PRODUCTS_DIR; }; 43B3E074266756540024822F /* libjunixsocket-amd64-unknown-openbsd6.9.so */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-amd64-unknown-openbsd6.9.so"; sourceTree = BUILT_PRODUCTS_DIR; }; 43B3E0AC266971630024822F /* libjunixsocket-sparc-sun-solaris2.11.so */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-sparc-sun-solaris2.11.so"; sourceTree = BUILT_PRODUCTS_DIR; }; + 43C57CFE2A38F370001DEA62 /* libjunixsocket-aarch64-linux-android30.so */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-aarch64-linux-android30.so"; sourceTree = BUILT_PRODUCTS_DIR; }; 43C5FB7D261AC3700088E301 /* libjunixsocket-arm64-apple-macos11.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-arm64-apple-macos11.dylib"; sourceTree = BUILT_PRODUCTS_DIR; }; 43D436BC261A564C002D0C12 /* libjunixsocket-x86_64-alpine-linux-musl.so */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjunixsocket-x86_64-alpine-linux-musl.so"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 430456CD2C91B81000CBAC42 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 4304A622266DFCEF003BCEDF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1329,6 +1761,27 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 435B549D2A3F65D2001011B8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 435B54D92A3F65F4001011B8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 435B55152A3F661F001011B8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 43663EDE261AECC400CEED08 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1385,6 +1838,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 43C57CFA2A38F370001DEA62 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 43C5FB79261AC3700088E301 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1411,6 +1871,8 @@ 430233A32650AB4900823167 /* jniutil.c */, 430233A12650AB4900823167 /* jniutil.h */, 430235BF2650AC3200823167 /* jni */, + 434234182C91C2890046DBD9 /* stat_wrapper.c */, + 434234332C91C4F50046DBD9 /* stat_wrapper.h */, ); name = "Build Essentials"; sourceTree = ""; @@ -1430,6 +1892,8 @@ 430233BB2650AB4C00823167 /* accept.h */, 4302339C2650AB4900823167 /* address.c */, 430233B92650AB4C00823167 /* address.h */, + 438F4D952A6AF05000546E2C /* afsystem.c */, + 438F4D962A6AF05000546E2C /* afsystem.h */, 430233A52650AB4900823167 /* ancillary.c */, 430233C52650AB4E00823167 /* ancillary.h */, 430233C32650AB4D00823167 /* bind.c */, @@ -1444,6 +1908,7 @@ 430233A82650AB4A00823167 /* exceptions.h */, 430233C42650AB4E00823167 /* filedescriptors.c */, 430233AC2650AB4A00823167 /* filedescriptors.h */, + 43145C4B2B0656E30090BD53 /* gettod_zos.h */, 430233B32650AB4B00823167 /* init.c */, 430233BE2650AB4D00823167 /* init.h */, 430233AF2650AB4B00823167 /* listen.c */, @@ -1515,6 +1980,11 @@ 436B5AB728244CB500574CD4 /* libjunixsocket-powerpc-ibm-aix7.2.so */, 436B5AF12826F49A00574CD4 /* libjunixsocket-powerpc-ibm-os400.so */, 43684DB7282EE24B005F2989 /* libjunixsocket-aarch64-w64-mingw32.so */, + 43C57CFE2A38F370001DEA62 /* libjunixsocket-aarch64-linux-android30.so */, + 435B54A12A3F65D2001011B8 /* libjunixsocket-x86_64-linux-android30.so */, + 435B54DD2A3F65F4001011B8 /* libjunixsocket-i686-linux-android30.so */, + 435B55192A3F661F001011B8 /* libjunixsocket-arm-linux-androideabi30.so */, + 430456D12C91B81000CBAC42 /* libjunixsocket-loongarch64-openEuler-linux.so */, ); name = Products; sourceTree = ""; @@ -1522,12 +1992,50 @@ /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ + 430456962C91B81000CBAC42 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 430456972C91B81000CBAC42 /* tipc.h in Headers */, + 430456982C91B81000CBAC42 /* jni.h in Headers */, + 4342344C2C91C4F50046DBD9 /* stat_wrapper.h in Headers */, + 430456992C91B81000CBAC42 /* ancillary.h in Headers */, + 4304569A2C91B81000CBAC42 /* pipe.h in Headers */, + 4304569B2C91B81000CBAC42 /* init.h in Headers */, + 4304569C2C91B81000CBAC42 /* send.h in Headers */, + 4304569D2C91B81000CBAC42 /* listen.h in Headers */, + 4304569E2C91B81000CBAC42 /* config.h in Headers */, + 4304569F2C91B81000CBAC42 /* socketpair.h in Headers */, + 430456A02C91B81000CBAC42 /* vsock.h in Headers */, + 430456A12C91B81000CBAC42 /* connect.h in Headers */, + 430456A22C91B81000CBAC42 /* jniutil.h in Headers */, + 430456A32C91B81000CBAC42 /* socketoptions.h in Headers */, + 430456A42C91B81000CBAC42 /* filedescriptors.h in Headers */, + 430456A52C91B81000CBAC42 /* exceptions.h in Headers */, + 430456A62C91B81000CBAC42 /* capabilities.h in Headers */, + 430456A72C91B81000CBAC42 /* gettod_zos.h in Headers */, + 430456A82C91B81000CBAC42 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, + 430456A92C91B81000CBAC42 /* socket.h in Headers */, + 430456AA2C91B81000CBAC42 /* ckmacros.h in Headers */, + 430456AB2C91B81000CBAC42 /* address.h in Headers */, + 430456AC2C91B81000CBAC42 /* afsystem.h in Headers */, + 430456AD2C91B81000CBAC42 /* polling.h in Headers */, + 430456AE2C91B81000CBAC42 /* accept.h in Headers */, + 430456AF2C91B81000CBAC42 /* reflection.h in Headers */, + 430456B02C91B81000CBAC42 /* jniport.h in Headers */, + 430456B12C91B81000CBAC42 /* receive.h in Headers */, + 430456B22C91B81000CBAC42 /* credentials.h in Headers */, + 430456B32C91B81000CBAC42 /* bind.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 4304A5F2266DFCEF003BCEDF /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 436E167127FBB70B00A553EC /* tipc.h in Headers */, 4304A5F3266DFCEF003BCEDF /* jni.h in Headers */, + 434234472C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 4304A5F4266DFCEF003BCEDF /* ancillary.h in Headers */, 4304A5F5266DFCEF003BCEDF /* pipe.h in Headers */, 4304A5F6266DFCEF003BCEDF /* init.h in Headers */, @@ -1542,10 +2050,12 @@ 4304A5FE266DFCEF003BCEDF /* filedescriptors.h in Headers */, 4304A5FF266DFCEF003BCEDF /* exceptions.h in Headers */, 4304A600266DFCEF003BCEDF /* capabilities.h in Headers */, + 43145C5F2B0656E30090BD53 /* gettod_zos.h in Headers */, 4304A601266DFCEF003BCEDF /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 4304A602266DFCEF003BCEDF /* socket.h in Headers */, 4304A603266DFCEF003BCEDF /* ckmacros.h in Headers */, 4304A604266DFCEF003BCEDF /* address.h in Headers */, + 438F4DC22A6AF05100546E2C /* afsystem.h in Headers */, 4304A605266DFCEF003BCEDF /* polling.h in Headers */, 4304A606266DFCEF003BCEDF /* accept.h in Headers */, 4304A607266DFCEF003BCEDF /* reflection.h in Headers */, @@ -1562,6 +2072,7 @@ files = ( 436E166227FBB70B00A553EC /* tipc.h in Headers */, 430235C32650AC3200823167 /* jni.h in Headers */, + 434234352C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 430235A82650AB4F00823167 /* ancillary.h in Headers */, 43B3DF84265EEC930024822F /* pipe.h in Headers */, 430235542650AB4F00823167 /* init.h in Headers */, @@ -1576,10 +2087,12 @@ 430234882650AB4E00823167 /* filedescriptors.h in Headers */, 430234582650AB4E00823167 /* exceptions.h in Headers */, 430234282650AB4E00823167 /* capabilities.h in Headers */, + 43145C4D2B0656E30090BD53 /* gettod_zos.h in Headers */, 4302356C2650AB4F00823167 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 430234E82650AB4F00823167 /* socket.h in Headers */, 430235602650AB4F00823167 /* ckmacros.h in Headers */, 430235182650AB4F00823167 /* address.h in Headers */, + 438F4DB02A6AF05100546E2C /* afsystem.h in Headers */, 430234642650AB4E00823167 /* polling.h in Headers */, 430235302650AB4F00823167 /* accept.h in Headers */, 430234942650AB4E00823167 /* reflection.h in Headers */, @@ -1596,6 +2109,7 @@ files = ( 436E166D27FBB70B00A553EC /* tipc.h in Headers */, 430235CD2650AC3200823167 /* jni.h in Headers */, + 434234432C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 430235B22650AB4F00823167 /* ancillary.h in Headers */, 43B3DF8E265EEC930024822F /* pipe.h in Headers */, 4302355E2650AB4F00823167 /* init.h in Headers */, @@ -1610,10 +2124,12 @@ 430234922650AB4E00823167 /* filedescriptors.h in Headers */, 430234622650AB4E00823167 /* exceptions.h in Headers */, 430234322650AB4E00823167 /* capabilities.h in Headers */, + 43145C5B2B0656E30090BD53 /* gettod_zos.h in Headers */, 430235762650AB4F00823167 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 430234F22650AB4F00823167 /* socket.h in Headers */, 4302356A2650AB4F00823167 /* ckmacros.h in Headers */, 430235222650AB4F00823167 /* address.h in Headers */, + 438F4DBE2A6AF05100546E2C /* afsystem.h in Headers */, 4302346E2650AB4E00823167 /* polling.h in Headers */, 4302353A2650AB4F00823167 /* accept.h in Headers */, 4302349E2650AB4E00823167 /* reflection.h in Headers */, @@ -1630,6 +2146,7 @@ files = ( 436E166627FBB70B00A553EC /* tipc.h in Headers */, 430235C62650AC3200823167 /* jni.h in Headers */, + 434234392C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 430235AB2650AB4F00823167 /* ancillary.h in Headers */, 43B3DF87265EEC930024822F /* pipe.h in Headers */, 430235572650AB4F00823167 /* init.h in Headers */, @@ -1644,10 +2161,12 @@ 4302348B2650AB4E00823167 /* filedescriptors.h in Headers */, 4302345B2650AB4E00823167 /* exceptions.h in Headers */, 4302342B2650AB4E00823167 /* capabilities.h in Headers */, + 43145C512B0656E30090BD53 /* gettod_zos.h in Headers */, 4302356F2650AB4F00823167 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 430234EB2650AB4F00823167 /* socket.h in Headers */, 430235632650AB4F00823167 /* ckmacros.h in Headers */, 4302351B2650AB4F00823167 /* address.h in Headers */, + 438F4DB42A6AF05100546E2C /* afsystem.h in Headers */, 430234672650AB4E00823167 /* polling.h in Headers */, 430235332650AB4F00823167 /* accept.h in Headers */, 430234972650AB4E00823167 /* reflection.h in Headers */, @@ -1664,6 +2183,7 @@ files = ( 436E166927FBB70B00A553EC /* tipc.h in Headers */, 430235C92650AC3200823167 /* jni.h in Headers */, + 4342343E2C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 430235AE2650AB4F00823167 /* ancillary.h in Headers */, 43B3DF8A265EEC930024822F /* pipe.h in Headers */, 4302355A2650AB4F00823167 /* init.h in Headers */, @@ -1678,10 +2198,12 @@ 4302348E2650AB4E00823167 /* filedescriptors.h in Headers */, 4302345E2650AB4E00823167 /* exceptions.h in Headers */, 4302342E2650AB4E00823167 /* capabilities.h in Headers */, + 43145C562B0656E30090BD53 /* gettod_zos.h in Headers */, 430235722650AB4F00823167 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 430234EE2650AB4F00823167 /* socket.h in Headers */, 430235662650AB4F00823167 /* ckmacros.h in Headers */, 4302351E2650AB4F00823167 /* address.h in Headers */, + 438F4DB92A6AF05100546E2C /* afsystem.h in Headers */, 4302346A2650AB4E00823167 /* polling.h in Headers */, 430235362650AB4F00823167 /* accept.h in Headers */, 4302349A2650AB4E00823167 /* reflection.h in Headers */, @@ -1698,6 +2220,7 @@ files = ( 436E166C27FBB70B00A553EC /* tipc.h in Headers */, 430235CC2650AC3200823167 /* jni.h in Headers */, + 434234412C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 430235B12650AB4F00823167 /* ancillary.h in Headers */, 43B3DF8D265EEC930024822F /* pipe.h in Headers */, 4302355D2650AB4F00823167 /* init.h in Headers */, @@ -1712,10 +2235,12 @@ 430234912650AB4E00823167 /* filedescriptors.h in Headers */, 430234612650AB4E00823167 /* exceptions.h in Headers */, 430234312650AB4E00823167 /* capabilities.h in Headers */, + 43145C592B0656E30090BD53 /* gettod_zos.h in Headers */, 430235752650AB4F00823167 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 430234F12650AB4F00823167 /* socket.h in Headers */, 430235692650AB4F00823167 /* ckmacros.h in Headers */, 430235212650AB4F00823167 /* address.h in Headers */, + 438F4DBC2A6AF05100546E2C /* afsystem.h in Headers */, 4302346D2650AB4E00823167 /* polling.h in Headers */, 430235392650AB4F00823167 /* accept.h in Headers */, 4302349D2650AB4E00823167 /* reflection.h in Headers */, @@ -1732,6 +2257,7 @@ files = ( 436E166727FBB70B00A553EC /* tipc.h in Headers */, 430235C72650AC3200823167 /* jni.h in Headers */, + 4342343A2C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 430235AC2650AB4F00823167 /* ancillary.h in Headers */, 43B3DF88265EEC930024822F /* pipe.h in Headers */, 430235582650AB4F00823167 /* init.h in Headers */, @@ -1746,10 +2272,12 @@ 4302348C2650AB4E00823167 /* filedescriptors.h in Headers */, 4302345C2650AB4E00823167 /* exceptions.h in Headers */, 4302342C2650AB4E00823167 /* capabilities.h in Headers */, + 43145C522B0656E30090BD53 /* gettod_zos.h in Headers */, 430235702650AB4F00823167 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 430234EC2650AB4F00823167 /* socket.h in Headers */, 430235642650AB4F00823167 /* ckmacros.h in Headers */, 4302351C2650AB4F00823167 /* address.h in Headers */, + 438F4DB52A6AF05100546E2C /* afsystem.h in Headers */, 430234682650AB4E00823167 /* polling.h in Headers */, 430235342650AB4F00823167 /* accept.h in Headers */, 430234982650AB4E00823167 /* reflection.h in Headers */, @@ -1766,6 +2294,7 @@ files = ( 436E166827FBB70B00A553EC /* tipc.h in Headers */, 430235C82650AC3200823167 /* jni.h in Headers */, + 4342343D2C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 430235AD2650AB4F00823167 /* ancillary.h in Headers */, 43B3DF89265EEC930024822F /* pipe.h in Headers */, 430235592650AB4F00823167 /* init.h in Headers */, @@ -1780,10 +2309,12 @@ 4302348D2650AB4E00823167 /* filedescriptors.h in Headers */, 4302345D2650AB4E00823167 /* exceptions.h in Headers */, 4302342D2650AB4E00823167 /* capabilities.h in Headers */, + 43145C552B0656E30090BD53 /* gettod_zos.h in Headers */, 430235712650AB4F00823167 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 430234ED2650AB4F00823167 /* socket.h in Headers */, 430235652650AB4F00823167 /* ckmacros.h in Headers */, 4302351D2650AB4F00823167 /* address.h in Headers */, + 438F4DB82A6AF05100546E2C /* afsystem.h in Headers */, 430234692650AB4E00823167 /* polling.h in Headers */, 430235352650AB4F00823167 /* accept.h in Headers */, 430234992650AB4E00823167 /* reflection.h in Headers */, @@ -1800,6 +2331,7 @@ files = ( 436E166527FBB70B00A553EC /* tipc.h in Headers */, 430235C52650AC3200823167 /* jni.h in Headers */, + 434234382C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 430235AA2650AB4F00823167 /* ancillary.h in Headers */, 43B3DF86265EEC930024822F /* pipe.h in Headers */, 430235562650AB4F00823167 /* init.h in Headers */, @@ -1814,10 +2346,12 @@ 4302348A2650AB4E00823167 /* filedescriptors.h in Headers */, 4302345A2650AB4E00823167 /* exceptions.h in Headers */, 4302342A2650AB4E00823167 /* capabilities.h in Headers */, + 43145C502B0656E30090BD53 /* gettod_zos.h in Headers */, 4302356E2650AB4F00823167 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 430234EA2650AB4F00823167 /* socket.h in Headers */, 430235622650AB4F00823167 /* ckmacros.h in Headers */, 4302351A2650AB4F00823167 /* address.h in Headers */, + 438F4DB32A6AF05100546E2C /* afsystem.h in Headers */, 430234662650AB4E00823167 /* polling.h in Headers */, 430235322650AB4F00823167 /* accept.h in Headers */, 430234962650AB4E00823167 /* reflection.h in Headers */, @@ -1834,6 +2368,7 @@ files = ( 436E166B27FBB70B00A553EC /* tipc.h in Headers */, 430235CB2650AC3200823167 /* jni.h in Headers */, + 434234402C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 430235B02650AB4F00823167 /* ancillary.h in Headers */, 43B3DF8C265EEC930024822F /* pipe.h in Headers */, 4302355C2650AB4F00823167 /* init.h in Headers */, @@ -1848,10 +2383,12 @@ 430234902650AB4E00823167 /* filedescriptors.h in Headers */, 430234602650AB4E00823167 /* exceptions.h in Headers */, 430234302650AB4E00823167 /* capabilities.h in Headers */, + 43145C582B0656E30090BD53 /* gettod_zos.h in Headers */, 430235742650AB4F00823167 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 430234F02650AB4F00823167 /* socket.h in Headers */, 430235682650AB4F00823167 /* ckmacros.h in Headers */, 430235202650AB4F00823167 /* address.h in Headers */, + 438F4DBB2A6AF05100546E2C /* afsystem.h in Headers */, 4302346C2650AB4E00823167 /* polling.h in Headers */, 430235382650AB4F00823167 /* accept.h in Headers */, 4302349C2650AB4E00823167 /* reflection.h in Headers */, @@ -1862,12 +2399,124 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 435B54692A3F65D2001011B8 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 435B546A2A3F65D2001011B8 /* tipc.h in Headers */, + 435B546B2A3F65D2001011B8 /* jni.h in Headers */, + 434234492C91C4F50046DBD9 /* stat_wrapper.h in Headers */, + 435B546C2A3F65D2001011B8 /* ancillary.h in Headers */, + 435B546D2A3F65D2001011B8 /* pipe.h in Headers */, + 435B546E2A3F65D2001011B8 /* init.h in Headers */, + 435B546F2A3F65D2001011B8 /* send.h in Headers */, + 435B54702A3F65D2001011B8 /* listen.h in Headers */, + 435B54712A3F65D2001011B8 /* config.h in Headers */, + 435B54722A3F65D2001011B8 /* socketpair.h in Headers */, + 435B54732A3F65D2001011B8 /* vsock.h in Headers */, + 435B54742A3F65D2001011B8 /* connect.h in Headers */, + 435B54752A3F65D2001011B8 /* jniutil.h in Headers */, + 435B54762A3F65D2001011B8 /* socketoptions.h in Headers */, + 435B54772A3F65D2001011B8 /* filedescriptors.h in Headers */, + 435B54782A3F65D2001011B8 /* exceptions.h in Headers */, + 435B54792A3F65D2001011B8 /* capabilities.h in Headers */, + 43145C612B0656E30090BD53 /* gettod_zos.h in Headers */, + 435B547A2A3F65D2001011B8 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, + 435B547B2A3F65D2001011B8 /* socket.h in Headers */, + 435B547C2A3F65D2001011B8 /* ckmacros.h in Headers */, + 435B547D2A3F65D2001011B8 /* address.h in Headers */, + 438F4DC42A6AF05100546E2C /* afsystem.h in Headers */, + 435B547E2A3F65D2001011B8 /* polling.h in Headers */, + 435B547F2A3F65D2001011B8 /* accept.h in Headers */, + 435B54802A3F65D2001011B8 /* reflection.h in Headers */, + 435B54812A3F65D2001011B8 /* jniport.h in Headers */, + 435B54822A3F65D2001011B8 /* receive.h in Headers */, + 435B54832A3F65D2001011B8 /* credentials.h in Headers */, + 435B54842A3F65D2001011B8 /* bind.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 435B54A52A3F65F4001011B8 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 435B54A62A3F65F4001011B8 /* tipc.h in Headers */, + 435B54A72A3F65F4001011B8 /* jni.h in Headers */, + 4342344A2C91C4F50046DBD9 /* stat_wrapper.h in Headers */, + 435B54A82A3F65F4001011B8 /* ancillary.h in Headers */, + 435B54A92A3F65F4001011B8 /* pipe.h in Headers */, + 435B54AA2A3F65F4001011B8 /* init.h in Headers */, + 435B54AB2A3F65F4001011B8 /* send.h in Headers */, + 435B54AC2A3F65F4001011B8 /* listen.h in Headers */, + 435B54AD2A3F65F4001011B8 /* config.h in Headers */, + 435B54AE2A3F65F4001011B8 /* socketpair.h in Headers */, + 435B54AF2A3F65F4001011B8 /* vsock.h in Headers */, + 435B54B02A3F65F4001011B8 /* connect.h in Headers */, + 435B54B12A3F65F4001011B8 /* jniutil.h in Headers */, + 435B54B22A3F65F4001011B8 /* socketoptions.h in Headers */, + 435B54B32A3F65F4001011B8 /* filedescriptors.h in Headers */, + 435B54B42A3F65F4001011B8 /* exceptions.h in Headers */, + 435B54B52A3F65F4001011B8 /* capabilities.h in Headers */, + 43145C622B0656E30090BD53 /* gettod_zos.h in Headers */, + 435B54B62A3F65F4001011B8 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, + 435B54B72A3F65F4001011B8 /* socket.h in Headers */, + 435B54B82A3F65F4001011B8 /* ckmacros.h in Headers */, + 435B54B92A3F65F4001011B8 /* address.h in Headers */, + 438F4DC52A6AF05100546E2C /* afsystem.h in Headers */, + 435B54BA2A3F65F4001011B8 /* polling.h in Headers */, + 435B54BB2A3F65F4001011B8 /* accept.h in Headers */, + 435B54BC2A3F65F4001011B8 /* reflection.h in Headers */, + 435B54BD2A3F65F4001011B8 /* jniport.h in Headers */, + 435B54BE2A3F65F4001011B8 /* receive.h in Headers */, + 435B54BF2A3F65F4001011B8 /* credentials.h in Headers */, + 435B54C02A3F65F4001011B8 /* bind.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 435B54E12A3F661F001011B8 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 435B54E22A3F661F001011B8 /* tipc.h in Headers */, + 435B54E32A3F661F001011B8 /* jni.h in Headers */, + 4342344B2C91C4F50046DBD9 /* stat_wrapper.h in Headers */, + 435B54E42A3F661F001011B8 /* ancillary.h in Headers */, + 435B54E52A3F661F001011B8 /* pipe.h in Headers */, + 435B54E62A3F661F001011B8 /* init.h in Headers */, + 435B54E72A3F661F001011B8 /* send.h in Headers */, + 435B54E82A3F661F001011B8 /* listen.h in Headers */, + 435B54E92A3F661F001011B8 /* config.h in Headers */, + 435B54EA2A3F661F001011B8 /* socketpair.h in Headers */, + 435B54EB2A3F661F001011B8 /* vsock.h in Headers */, + 435B54EC2A3F661F001011B8 /* connect.h in Headers */, + 435B54ED2A3F661F001011B8 /* jniutil.h in Headers */, + 435B54EE2A3F661F001011B8 /* socketoptions.h in Headers */, + 435B54EF2A3F661F001011B8 /* filedescriptors.h in Headers */, + 435B54F02A3F661F001011B8 /* exceptions.h in Headers */, + 435B54F12A3F661F001011B8 /* capabilities.h in Headers */, + 43145C632B0656E30090BD53 /* gettod_zos.h in Headers */, + 435B54F22A3F661F001011B8 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, + 435B54F32A3F661F001011B8 /* socket.h in Headers */, + 435B54F42A3F661F001011B8 /* ckmacros.h in Headers */, + 435B54F52A3F661F001011B8 /* address.h in Headers */, + 438F4DC62A6AF05100546E2C /* afsystem.h in Headers */, + 435B54F62A3F661F001011B8 /* polling.h in Headers */, + 435B54F72A3F661F001011B8 /* accept.h in Headers */, + 435B54F82A3F661F001011B8 /* reflection.h in Headers */, + 435B54F92A3F661F001011B8 /* jniport.h in Headers */, + 435B54FA2A3F661F001011B8 /* receive.h in Headers */, + 435B54FB2A3F661F001011B8 /* credentials.h in Headers */, + 435B54FC2A3F661F001011B8 /* bind.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 43663ED7261AECC400CEED08 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 436E166127FBB70B00A553EC /* tipc.h in Headers */, 430235C22650AC3200823167 /* jni.h in Headers */, + 434234342C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 430235A72650AB4F00823167 /* ancillary.h in Headers */, 43B3DF83265EEC930024822F /* pipe.h in Headers */, 430235532650AB4F00823167 /* init.h in Headers */, @@ -1882,10 +2531,12 @@ 430234872650AB4E00823167 /* filedescriptors.h in Headers */, 430234572650AB4E00823167 /* exceptions.h in Headers */, 430234272650AB4E00823167 /* capabilities.h in Headers */, + 43145C4C2B0656E30090BD53 /* gettod_zos.h in Headers */, 4302356B2650AB4F00823167 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 430234E72650AB4F00823167 /* socket.h in Headers */, 4302355F2650AB4F00823167 /* ckmacros.h in Headers */, 430235172650AB4F00823167 /* address.h in Headers */, + 438F4DAF2A6AF05100546E2C /* afsystem.h in Headers */, 430234632650AB4E00823167 /* polling.h in Headers */, 4302352F2650AB4F00823167 /* accept.h in Headers */, 430234932650AB4E00823167 /* reflection.h in Headers */, @@ -1902,6 +2553,7 @@ files = ( 43684D82282EE24B005F2989 /* tipc.h in Headers */, 43684D83282EE24B005F2989 /* jni.h in Headers */, + 434234422C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 43684D84282EE24B005F2989 /* ancillary.h in Headers */, 43684D85282EE24B005F2989 /* pipe.h in Headers */, 43684D86282EE24B005F2989 /* init.h in Headers */, @@ -1916,10 +2568,12 @@ 43684D8E282EE24B005F2989 /* filedescriptors.h in Headers */, 43684D8F282EE24B005F2989 /* exceptions.h in Headers */, 43684D90282EE24B005F2989 /* capabilities.h in Headers */, + 43145C5A2B0656E30090BD53 /* gettod_zos.h in Headers */, 43684D91282EE24B005F2989 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 43684D92282EE24B005F2989 /* socket.h in Headers */, 43684D93282EE24B005F2989 /* ckmacros.h in Headers */, 43684D94282EE24B005F2989 /* address.h in Headers */, + 438F4DBD2A6AF05100546E2C /* afsystem.h in Headers */, 43684D95282EE24B005F2989 /* polling.h in Headers */, 43684D96282EE24B005F2989 /* accept.h in Headers */, 43684D97282EE24B005F2989 /* reflection.h in Headers */, @@ -1936,6 +2590,7 @@ files = ( 436B5A8228244CB500574CD4 /* tipc.h in Headers */, 436B5A8328244CB500574CD4 /* jni.h in Headers */, + 4342343B2C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 436B5A8428244CB500574CD4 /* ancillary.h in Headers */, 436B5A8528244CB500574CD4 /* pipe.h in Headers */, 436B5A8628244CB500574CD4 /* init.h in Headers */, @@ -1950,10 +2605,12 @@ 436B5A8E28244CB500574CD4 /* filedescriptors.h in Headers */, 436B5A8F28244CB500574CD4 /* exceptions.h in Headers */, 436B5A9028244CB500574CD4 /* capabilities.h in Headers */, + 43145C532B0656E30090BD53 /* gettod_zos.h in Headers */, 436B5A9128244CB500574CD4 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 436B5A9228244CB500574CD4 /* socket.h in Headers */, 436B5A9328244CB500574CD4 /* ckmacros.h in Headers */, 436B5A9428244CB500574CD4 /* address.h in Headers */, + 438F4DB62A6AF05100546E2C /* afsystem.h in Headers */, 436B5A9528244CB500574CD4 /* polling.h in Headers */, 436B5A9628244CB500574CD4 /* accept.h in Headers */, 436B5A9728244CB500574CD4 /* reflection.h in Headers */, @@ -1970,6 +2627,7 @@ files = ( 436B5ABC2826F49A00574CD4 /* tipc.h in Headers */, 436B5ABD2826F49A00574CD4 /* jni.h in Headers */, + 4342343C2C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 436B5ABE2826F49A00574CD4 /* ancillary.h in Headers */, 436B5ABF2826F49A00574CD4 /* pipe.h in Headers */, 436B5AC02826F49A00574CD4 /* init.h in Headers */, @@ -1984,10 +2642,12 @@ 436B5AC82826F49A00574CD4 /* filedescriptors.h in Headers */, 436B5AC92826F49A00574CD4 /* exceptions.h in Headers */, 436B5ACA2826F49A00574CD4 /* capabilities.h in Headers */, + 43145C542B0656E30090BD53 /* gettod_zos.h in Headers */, 436B5ACB2826F49A00574CD4 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 436B5ACC2826F49A00574CD4 /* socket.h in Headers */, 436B5ACD2826F49A00574CD4 /* ckmacros.h in Headers */, 436B5ACE2826F49A00574CD4 /* address.h in Headers */, + 438F4DB72A6AF05100546E2C /* afsystem.h in Headers */, 436B5ACF2826F49A00574CD4 /* polling.h in Headers */, 436B5AD02826F49A00574CD4 /* accept.h in Headers */, 436B5AD12826F49A00574CD4 /* reflection.h in Headers */, @@ -2004,6 +2664,7 @@ files = ( 436E166E27FBB70B00A553EC /* tipc.h in Headers */, 43B3DFD126660A2D0024822F /* jni.h in Headers */, + 434234442C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 43B3DFD226660A2D0024822F /* ancillary.h in Headers */, 43B3DFD326660A2D0024822F /* pipe.h in Headers */, 43B3DFD426660A2D0024822F /* init.h in Headers */, @@ -2018,10 +2679,12 @@ 43B3DFDC26660A2D0024822F /* filedescriptors.h in Headers */, 43B3DFDD26660A2D0024822F /* exceptions.h in Headers */, 43B3DFDE26660A2D0024822F /* capabilities.h in Headers */, + 43145C5C2B0656E30090BD53 /* gettod_zos.h in Headers */, 43B3DFDF26660A2D0024822F /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 43B3DFE026660A2D0024822F /* socket.h in Headers */, 43B3DFE126660A2D0024822F /* ckmacros.h in Headers */, 43B3DFE226660A2D0024822F /* address.h in Headers */, + 438F4DBF2A6AF05100546E2C /* afsystem.h in Headers */, 43B3DFE326660A2D0024822F /* polling.h in Headers */, 43B3DFE426660A2D0024822F /* accept.h in Headers */, 43B3DFE526660A2D0024822F /* reflection.h in Headers */, @@ -2038,6 +2701,7 @@ files = ( 436E166F27FBB70B00A553EC /* tipc.h in Headers */, 43B3E00926672FD30024822F /* jni.h in Headers */, + 434234452C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 43B3E00A26672FD30024822F /* ancillary.h in Headers */, 43B3E00B26672FD30024822F /* pipe.h in Headers */, 43B3E00C26672FD30024822F /* init.h in Headers */, @@ -2052,10 +2716,12 @@ 43B3E01426672FD30024822F /* filedescriptors.h in Headers */, 43B3E01526672FD30024822F /* exceptions.h in Headers */, 43B3E01626672FD30024822F /* capabilities.h in Headers */, + 43145C5D2B0656E30090BD53 /* gettod_zos.h in Headers */, 43B3E01726672FD30024822F /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 43B3E01826672FD30024822F /* socket.h in Headers */, 43B3E01926672FD30024822F /* ckmacros.h in Headers */, 43B3E01A26672FD30024822F /* address.h in Headers */, + 438F4DC02A6AF05100546E2C /* afsystem.h in Headers */, 43B3E01B26672FD30024822F /* polling.h in Headers */, 43B3E01C26672FD30024822F /* accept.h in Headers */, 43B3E01D26672FD30024822F /* reflection.h in Headers */, @@ -2072,6 +2738,7 @@ files = ( 436E166327FBB70B00A553EC /* tipc.h in Headers */, 43B3E041266756540024822F /* jni.h in Headers */, + 434234362C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 43B3E042266756540024822F /* ancillary.h in Headers */, 43B3E043266756540024822F /* pipe.h in Headers */, 43B3E044266756540024822F /* init.h in Headers */, @@ -2086,10 +2753,12 @@ 43B3E04C266756540024822F /* filedescriptors.h in Headers */, 43B3E04D266756540024822F /* exceptions.h in Headers */, 43B3E04E266756540024822F /* capabilities.h in Headers */, + 43145C4E2B0656E30090BD53 /* gettod_zos.h in Headers */, 43B3E04F266756540024822F /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 43B3E050266756540024822F /* socket.h in Headers */, 43B3E051266756540024822F /* ckmacros.h in Headers */, 43B3E052266756540024822F /* address.h in Headers */, + 438F4DB12A6AF05100546E2C /* afsystem.h in Headers */, 43B3E053266756540024822F /* polling.h in Headers */, 43B3E054266756540024822F /* accept.h in Headers */, 43B3E055266756540024822F /* reflection.h in Headers */, @@ -2106,6 +2775,7 @@ files = ( 436E167027FBB70B00A553EC /* tipc.h in Headers */, 43B3E079266971630024822F /* jni.h in Headers */, + 434234462C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 43B3E07A266971630024822F /* ancillary.h in Headers */, 43B3E07B266971630024822F /* pipe.h in Headers */, 43B3E07C266971630024822F /* init.h in Headers */, @@ -2120,10 +2790,12 @@ 43B3E084266971630024822F /* filedescriptors.h in Headers */, 43B3E085266971630024822F /* exceptions.h in Headers */, 43B3E086266971630024822F /* capabilities.h in Headers */, + 43145C5E2B0656E30090BD53 /* gettod_zos.h in Headers */, 43B3E087266971630024822F /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 43B3E088266971630024822F /* socket.h in Headers */, 43B3E089266971630024822F /* ckmacros.h in Headers */, 43B3E08A266971630024822F /* address.h in Headers */, + 438F4DC12A6AF05100546E2C /* afsystem.h in Headers */, 43B3E08B266971630024822F /* polling.h in Headers */, 43B3E08C266971630024822F /* accept.h in Headers */, 43B3E08D266971630024822F /* reflection.h in Headers */, @@ -2134,12 +2806,50 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 43C57CC62A38F370001DEA62 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 43C57CC72A38F370001DEA62 /* tipc.h in Headers */, + 43C57CC82A38F370001DEA62 /* jni.h in Headers */, + 434234482C91C4F50046DBD9 /* stat_wrapper.h in Headers */, + 43C57CC92A38F370001DEA62 /* ancillary.h in Headers */, + 43C57CCA2A38F370001DEA62 /* pipe.h in Headers */, + 43C57CCB2A38F370001DEA62 /* init.h in Headers */, + 43C57CCC2A38F370001DEA62 /* send.h in Headers */, + 43C57CCD2A38F370001DEA62 /* listen.h in Headers */, + 43C57CCE2A38F370001DEA62 /* config.h in Headers */, + 43C57CCF2A38F370001DEA62 /* socketpair.h in Headers */, + 43C57CD02A38F370001DEA62 /* vsock.h in Headers */, + 43C57CD12A38F370001DEA62 /* connect.h in Headers */, + 43C57CD22A38F370001DEA62 /* jniutil.h in Headers */, + 43C57CD32A38F370001DEA62 /* socketoptions.h in Headers */, + 43C57CD42A38F370001DEA62 /* filedescriptors.h in Headers */, + 43C57CD52A38F370001DEA62 /* exceptions.h in Headers */, + 43C57CD62A38F370001DEA62 /* capabilities.h in Headers */, + 43145C602B0656E30090BD53 /* gettod_zos.h in Headers */, + 43C57CD72A38F370001DEA62 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, + 43C57CD82A38F370001DEA62 /* socket.h in Headers */, + 43C57CD92A38F370001DEA62 /* ckmacros.h in Headers */, + 43C57CDA2A38F370001DEA62 /* address.h in Headers */, + 438F4DC32A6AF05100546E2C /* afsystem.h in Headers */, + 43C57CDB2A38F370001DEA62 /* polling.h in Headers */, + 43C57CDC2A38F370001DEA62 /* accept.h in Headers */, + 43C57CDD2A38F370001DEA62 /* reflection.h in Headers */, + 43C57CDE2A38F370001DEA62 /* jniport.h in Headers */, + 43C57CDF2A38F370001DEA62 /* receive.h in Headers */, + 43C57CE02A38F370001DEA62 /* credentials.h in Headers */, + 43C57CE12A38F370001DEA62 /* bind.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 43C5FB72261AC3700088E301 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 436E166427FBB70B00A553EC /* tipc.h in Headers */, 430235C42650AC3200823167 /* jni.h in Headers */, + 434234372C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 430235A92650AB4F00823167 /* ancillary.h in Headers */, 43B3DF85265EEC930024822F /* pipe.h in Headers */, 430235552650AB4F00823167 /* init.h in Headers */, @@ -2154,10 +2864,12 @@ 430234892650AB4E00823167 /* filedescriptors.h in Headers */, 430234592650AB4E00823167 /* exceptions.h in Headers */, 430234292650AB4E00823167 /* capabilities.h in Headers */, + 43145C4F2B0656E30090BD53 /* gettod_zos.h in Headers */, 4302356D2650AB4F00823167 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 430234E92650AB4F00823167 /* socket.h in Headers */, 430235612650AB4F00823167 /* ckmacros.h in Headers */, 430235192650AB4F00823167 /* address.h in Headers */, + 438F4DB22A6AF05100546E2C /* afsystem.h in Headers */, 430234652650AB4E00823167 /* polling.h in Headers */, 430235312650AB4F00823167 /* accept.h in Headers */, 430234952650AB4E00823167 /* reflection.h in Headers */, @@ -2174,6 +2886,7 @@ files = ( 436E166A27FBB70B00A553EC /* tipc.h in Headers */, 430235CA2650AC3200823167 /* jni.h in Headers */, + 4342343F2C91C4F50046DBD9 /* stat_wrapper.h in Headers */, 430235AF2650AB4F00823167 /* ancillary.h in Headers */, 43B3DF8B265EEC930024822F /* pipe.h in Headers */, 4302355B2650AB4F00823167 /* init.h in Headers */, @@ -2188,10 +2901,12 @@ 4302348F2650AB4E00823167 /* filedescriptors.h in Headers */, 4302345F2650AB4E00823167 /* exceptions.h in Headers */, 4302342F2650AB4E00823167 /* capabilities.h in Headers */, + 43145C572B0656E30090BD53 /* gettod_zos.h in Headers */, 430235732650AB4F00823167 /* org_newsclub_net_unix_NativeUnixSocket.h in Headers */, 430234EF2650AB4F00823167 /* socket.h in Headers */, 430235672650AB4F00823167 /* ckmacros.h in Headers */, 4302351F2650AB4F00823167 /* address.h in Headers */, + 438F4DBA2A6AF05100546E2C /* afsystem.h in Headers */, 4302346B2650AB4E00823167 /* polling.h in Headers */, 430235372650AB4F00823167 /* accept.h in Headers */, 4302349B2650AB4E00823167 /* reflection.h in Headers */, @@ -2205,6 +2920,23 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 430456952C91B81000CBAC42 /* loongarch64-openEuler-linux */ = { + isa = PBXNativeTarget; + buildConfigurationList = 430456CE2C91B81000CBAC42 /* Build configuration list for PBXNativeTarget "loongarch64-openEuler-linux" */; + buildPhases = ( + 430456962C91B81000CBAC42 /* Headers */, + 430456B42C91B81000CBAC42 /* Sources */, + 430456CD2C91B81000CBAC42 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "loongarch64-openEuler-linux"; + productName = "junixsocket-native"; + productReference = 430456D12C91B81000CBAC42 /* libjunixsocket-loongarch64-openEuler-linux.so */; + productType = "com.apple.product-type.library.dynamic"; + }; 4304A5F1266DFCEF003BCEDF /* x86_64-portbld-dragonfly6.0 */ = { isa = PBXNativeTarget; buildConfigurationList = 4304A623266DFCEF003BCEDF /* Build configuration list for PBXNativeTarget "x86_64-portbld-dragonfly6.0" */; @@ -2375,6 +3107,57 @@ productReference = 432EF212261AE29200F4A583 /* libjunixsocket-x86_64-apple-darwin18.2.0.dylib */; productType = "com.apple.product-type.library.dynamic"; }; + 435B54682A3F65D2001011B8 /* x86_64-linux-android30 */ = { + isa = PBXNativeTarget; + buildConfigurationList = 435B549E2A3F65D2001011B8 /* Build configuration list for PBXNativeTarget "x86_64-linux-android30" */; + buildPhases = ( + 435B54692A3F65D2001011B8 /* Headers */, + 435B54852A3F65D2001011B8 /* Sources */, + 435B549D2A3F65D2001011B8 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "x86_64-linux-android30"; + productName = "junixsocket-native"; + productReference = 435B54A12A3F65D2001011B8 /* libjunixsocket-x86_64-linux-android30.so */; + productType = "com.apple.product-type.library.dynamic"; + }; + 435B54A42A3F65F4001011B8 /* i686-linux-android30 */ = { + isa = PBXNativeTarget; + buildConfigurationList = 435B54DA2A3F65F4001011B8 /* Build configuration list for PBXNativeTarget "i686-linux-android30" */; + buildPhases = ( + 435B54A52A3F65F4001011B8 /* Headers */, + 435B54C12A3F65F4001011B8 /* Sources */, + 435B54D92A3F65F4001011B8 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "i686-linux-android30"; + productName = "junixsocket-native"; + productReference = 435B54DD2A3F65F4001011B8 /* libjunixsocket-i686-linux-android30.so */; + productType = "com.apple.product-type.library.dynamic"; + }; + 435B54E02A3F661F001011B8 /* arm-linux-androideabi30 */ = { + isa = PBXNativeTarget; + buildConfigurationList = 435B55162A3F661F001011B8 /* Build configuration list for PBXNativeTarget "arm-linux-androideabi30" */; + buildPhases = ( + 435B54E12A3F661F001011B8 /* Headers */, + 435B54FD2A3F661F001011B8 /* Sources */, + 435B55152A3F661F001011B8 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "arm-linux-androideabi30"; + productName = "junixsocket-native"; + productReference = 435B55192A3F661F001011B8 /* libjunixsocket-arm-linux-androideabi30.so */; + productType = "com.apple.product-type.library.dynamic"; + }; 43663ED6261AECC400CEED08 /* current */ = { isa = PBXNativeTarget; buildConfigurationList = 43663EDF261AECC400CEED08 /* Build configuration list for PBXNativeTarget "current" */; @@ -2511,6 +3294,23 @@ productReference = 43B3E0AC266971630024822F /* libjunixsocket-sparc-sun-solaris2.11.so */; productType = "com.apple.product-type.library.dynamic"; }; + 43C57CC52A38F370001DEA62 /* aarch64-linux-android30 */ = { + isa = PBXNativeTarget; + buildConfigurationList = 43C57CFB2A38F370001DEA62 /* Build configuration list for PBXNativeTarget "aarch64-linux-android30" */; + buildPhases = ( + 43C57CC62A38F370001DEA62 /* Headers */, + 43C57CE22A38F370001DEA62 /* Sources */, + 43C57CFA2A38F370001DEA62 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "aarch64-linux-android30"; + productName = "junixsocket-native"; + productReference = 43C57CFE2A38F370001DEA62 /* libjunixsocket-aarch64-linux-android30.so */; + productType = "com.apple.product-type.library.dynamic"; + }; 43C5FB71261AC3700088E301 /* arm64-apple-macos11 */ = { isa = PBXNativeTarget; buildConfigurationList = 43C5FB7A261AC3700088E301 /* Build configuration list for PBXNativeTarget "arm64-apple-macos11" */; @@ -2551,7 +3351,8 @@ 43D436B0261A55D0002D0C12 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1320; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1540; TargetAttributes = { 432EF186261AD52100F4A583 = { CreatedOnToolsVersion = 11.3.1; @@ -2595,11 +3396,48 @@ 43B3E00726672FD30024822F /* x86_64-unknown-linux-gnu */, 43B3E077266971630024822F /* sparc-sun-solaris2.11 */, 4304A5F1266DFCEF003BCEDF /* x86_64-portbld-dragonfly6.0 */, + 43C57CC52A38F370001DEA62 /* aarch64-linux-android30 */, + 435B54682A3F65D2001011B8 /* x86_64-linux-android30 */, + 435B54A42A3F65F4001011B8 /* i686-linux-android30 */, + 435B54E02A3F661F001011B8 /* arm-linux-androideabi30 */, + 430456952C91B81000CBAC42 /* loongarch64-openEuler-linux */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ + 430456B42C91B81000CBAC42 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 430456B52C91B81000CBAC42 /* filedescriptors.c in Sources */, + 430456B62C91B81000CBAC42 /* bind.c in Sources */, + 430456B72C91B81000CBAC42 /* address.c in Sources */, + 430456B82C91B81000CBAC42 /* vsock.c in Sources */, + 430456B92C91B81000CBAC42 /* send.c in Sources */, + 430456BA2C91B81000CBAC42 /* socket.c in Sources */, + 430456BB2C91B81000CBAC42 /* pipe.c in Sources */, + 430456BC2C91B81000CBAC42 /* connect.c in Sources */, + 430456BD2C91B81000CBAC42 /* capabilities.c in Sources */, + 430456BE2C91B81000CBAC42 /* socketoptions.c in Sources */, + 430456BF2C91B81000CBAC42 /* polling.c in Sources */, + 430456C02C91B81000CBAC42 /* receive.c in Sources */, + 430456C12C91B81000CBAC42 /* jniutil.c in Sources */, + 430456C22C91B81000CBAC42 /* init.c in Sources */, + 430456C32C91B81000CBAC42 /* tipc.c in Sources */, + 430456C42C91B81000CBAC42 /* config.c in Sources */, + 430456C52C91B81000CBAC42 /* credentials.c in Sources */, + 434234312C91C2890046DBD9 /* stat_wrapper.c in Sources */, + 430456C62C91B81000CBAC42 /* socketpair.c in Sources */, + 430456C72C91B81000CBAC42 /* exceptions.c in Sources */, + 430456C82C91B81000CBAC42 /* reflection.c in Sources */, + 430456C92C91B81000CBAC42 /* listen.c in Sources */, + 430456CA2C91B81000CBAC42 /* ancillary.c in Sources */, + 430456CB2C91B81000CBAC42 /* accept.c in Sources */, + 430456CC2C91B81000CBAC42 /* afsystem.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 4304A60C266DFCEF003BCEDF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -2621,12 +3459,14 @@ 436E168227FBB70B00A553EC /* tipc.c in Sources */, 4304A61A266DFCEF003BCEDF /* config.c in Sources */, 4304A61B266DFCEF003BCEDF /* credentials.c in Sources */, + 4342342C2C91C2890046DBD9 /* stat_wrapper.c in Sources */, 4304A61C266DFCEF003BCEDF /* socketpair.c in Sources */, 4304A61D266DFCEF003BCEDF /* exceptions.c in Sources */, 4304A61E266DFCEF003BCEDF /* reflection.c in Sources */, 4304A61F266DFCEF003BCEDF /* listen.c in Sources */, 4304A620266DFCEF003BCEDF /* ancillary.c in Sources */, 4304A621266DFCEF003BCEDF /* accept.c in Sources */, + 438F4DAA2A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2651,12 +3491,14 @@ 436E167327FBB70B00A553EC /* tipc.c in Sources */, 430233F82650AB4E00823167 /* config.c in Sources */, 430235B42650AB4F00823167 /* credentials.c in Sources */, + 4342341A2C91C2890046DBD9 /* stat_wrapper.c in Sources */, 43B3DFB8266082000024822F /* socketpair.c in Sources */, 430235482650AB4F00823167 /* exceptions.c in Sources */, 430235242650AB4F00823167 /* reflection.c in Sources */, 430234AC2650AB4E00823167 /* listen.c in Sources */, 430234342650AB4E00823167 /* ancillary.c in Sources */, 430233E02650AB4E00823167 /* accept.c in Sources */, + 438F4D982A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2681,12 +3523,14 @@ 436E167E27FBB70B00A553EC /* tipc.c in Sources */, 430234022650AB4E00823167 /* config.c in Sources */, 430235BE2650AB4F00823167 /* credentials.c in Sources */, + 434234282C91C2890046DBD9 /* stat_wrapper.c in Sources */, 43B3DFC2266082000024822F /* socketpair.c in Sources */, 430235522650AB4F00823167 /* exceptions.c in Sources */, 4302352E2650AB4F00823167 /* reflection.c in Sources */, 430234B62650AB4E00823167 /* listen.c in Sources */, 4302343E2650AB4E00823167 /* ancillary.c in Sources */, 430233EA2650AB4E00823167 /* accept.c in Sources */, + 438F4DA62A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2711,12 +3555,14 @@ 436E167727FBB70B00A553EC /* tipc.c in Sources */, 430233FB2650AB4E00823167 /* config.c in Sources */, 430235B72650AB4F00823167 /* credentials.c in Sources */, + 4342341E2C91C2890046DBD9 /* stat_wrapper.c in Sources */, 43B3DFBB266082000024822F /* socketpair.c in Sources */, 4302354B2650AB4F00823167 /* exceptions.c in Sources */, 430235272650AB4F00823167 /* reflection.c in Sources */, 430234AF2650AB4E00823167 /* listen.c in Sources */, 430234372650AB4E00823167 /* ancillary.c in Sources */, 430233E32650AB4E00823167 /* accept.c in Sources */, + 438F4D9C2A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2741,12 +3587,14 @@ 436E167A27FBB70B00A553EC /* tipc.c in Sources */, 430233FE2650AB4E00823167 /* config.c in Sources */, 430235BA2650AB4F00823167 /* credentials.c in Sources */, + 434234232C91C2890046DBD9 /* stat_wrapper.c in Sources */, 43B3DFBE266082000024822F /* socketpair.c in Sources */, 4302354E2650AB4F00823167 /* exceptions.c in Sources */, 4302352A2650AB4F00823167 /* reflection.c in Sources */, 430234B22650AB4E00823167 /* listen.c in Sources */, 4302343A2650AB4E00823167 /* ancillary.c in Sources */, 430233E62650AB4E00823167 /* accept.c in Sources */, + 438F4DA12A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2771,12 +3619,14 @@ 436E167D27FBB70B00A553EC /* tipc.c in Sources */, 430234012650AB4E00823167 /* config.c in Sources */, 430235BD2650AB4F00823167 /* credentials.c in Sources */, + 434234262C91C2890046DBD9 /* stat_wrapper.c in Sources */, 43B3DFC1266082000024822F /* socketpair.c in Sources */, 430235512650AB4F00823167 /* exceptions.c in Sources */, 4302352D2650AB4F00823167 /* reflection.c in Sources */, 430234B52650AB4E00823167 /* listen.c in Sources */, 4302343D2650AB4E00823167 /* ancillary.c in Sources */, 430233E92650AB4E00823167 /* accept.c in Sources */, + 438F4DA42A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2801,12 +3651,14 @@ 436E167827FBB70B00A553EC /* tipc.c in Sources */, 430233FC2650AB4E00823167 /* config.c in Sources */, 430235B82650AB4F00823167 /* credentials.c in Sources */, + 4342341F2C91C2890046DBD9 /* stat_wrapper.c in Sources */, 43B3DFBC266082000024822F /* socketpair.c in Sources */, 4302354C2650AB4F00823167 /* exceptions.c in Sources */, 430235282650AB4F00823167 /* reflection.c in Sources */, 430234B02650AB4E00823167 /* listen.c in Sources */, 430234382650AB4E00823167 /* ancillary.c in Sources */, 430233E42650AB4E00823167 /* accept.c in Sources */, + 438F4D9D2A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2831,12 +3683,14 @@ 436E167927FBB70B00A553EC /* tipc.c in Sources */, 430233FD2650AB4E00823167 /* config.c in Sources */, 430235B92650AB4F00823167 /* credentials.c in Sources */, + 434234222C91C2890046DBD9 /* stat_wrapper.c in Sources */, 43B3DFBD266082000024822F /* socketpair.c in Sources */, 4302354D2650AB4F00823167 /* exceptions.c in Sources */, 430235292650AB4F00823167 /* reflection.c in Sources */, 430234B12650AB4E00823167 /* listen.c in Sources */, 430234392650AB4E00823167 /* ancillary.c in Sources */, 430233E52650AB4E00823167 /* accept.c in Sources */, + 438F4DA02A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2861,12 +3715,14 @@ 436E167627FBB70B00A553EC /* tipc.c in Sources */, 430233FA2650AB4E00823167 /* config.c in Sources */, 430235B62650AB4F00823167 /* credentials.c in Sources */, + 4342341D2C91C2890046DBD9 /* stat_wrapper.c in Sources */, 43B3DFBA266082000024822F /* socketpair.c in Sources */, 4302354A2650AB4F00823167 /* exceptions.c in Sources */, 430235262650AB4F00823167 /* reflection.c in Sources */, 430234AE2650AB4E00823167 /* listen.c in Sources */, 430234362650AB4E00823167 /* ancillary.c in Sources */, 430233E22650AB4E00823167 /* accept.c in Sources */, + 438F4D9B2A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2891,12 +3747,110 @@ 436E167C27FBB70B00A553EC /* tipc.c in Sources */, 430234002650AB4E00823167 /* config.c in Sources */, 430235BC2650AB4F00823167 /* credentials.c in Sources */, + 434234252C91C2890046DBD9 /* stat_wrapper.c in Sources */, 43B3DFC0266082000024822F /* socketpair.c in Sources */, 430235502650AB4F00823167 /* exceptions.c in Sources */, 4302352C2650AB4F00823167 /* reflection.c in Sources */, 430234B42650AB4E00823167 /* listen.c in Sources */, 4302343C2650AB4E00823167 /* ancillary.c in Sources */, 430233E82650AB4E00823167 /* accept.c in Sources */, + 438F4DA32A6AF05000546E2C /* afsystem.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 435B54852A3F65D2001011B8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 435B54862A3F65D2001011B8 /* filedescriptors.c in Sources */, + 435B54872A3F65D2001011B8 /* bind.c in Sources */, + 435B54882A3F65D2001011B8 /* address.c in Sources */, + 435B54892A3F65D2001011B8 /* vsock.c in Sources */, + 435B548A2A3F65D2001011B8 /* send.c in Sources */, + 435B548B2A3F65D2001011B8 /* socket.c in Sources */, + 435B548C2A3F65D2001011B8 /* pipe.c in Sources */, + 435B548D2A3F65D2001011B8 /* connect.c in Sources */, + 435B548E2A3F65D2001011B8 /* capabilities.c in Sources */, + 435B548F2A3F65D2001011B8 /* socketoptions.c in Sources */, + 435B54902A3F65D2001011B8 /* polling.c in Sources */, + 435B54912A3F65D2001011B8 /* receive.c in Sources */, + 435B54922A3F65D2001011B8 /* jniutil.c in Sources */, + 435B54932A3F65D2001011B8 /* init.c in Sources */, + 435B54942A3F65D2001011B8 /* tipc.c in Sources */, + 435B54952A3F65D2001011B8 /* config.c in Sources */, + 435B54962A3F65D2001011B8 /* credentials.c in Sources */, + 4342342E2C91C2890046DBD9 /* stat_wrapper.c in Sources */, + 435B54972A3F65D2001011B8 /* socketpair.c in Sources */, + 435B54982A3F65D2001011B8 /* exceptions.c in Sources */, + 435B54992A3F65D2001011B8 /* reflection.c in Sources */, + 435B549A2A3F65D2001011B8 /* listen.c in Sources */, + 435B549B2A3F65D2001011B8 /* ancillary.c in Sources */, + 435B549C2A3F65D2001011B8 /* accept.c in Sources */, + 438F4DAC2A6AF05000546E2C /* afsystem.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 435B54C12A3F65F4001011B8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 435B54C22A3F65F4001011B8 /* filedescriptors.c in Sources */, + 435B54C32A3F65F4001011B8 /* bind.c in Sources */, + 435B54C42A3F65F4001011B8 /* address.c in Sources */, + 435B54C52A3F65F4001011B8 /* vsock.c in Sources */, + 435B54C62A3F65F4001011B8 /* send.c in Sources */, + 435B54C72A3F65F4001011B8 /* socket.c in Sources */, + 435B54C82A3F65F4001011B8 /* pipe.c in Sources */, + 435B54C92A3F65F4001011B8 /* connect.c in Sources */, + 435B54CA2A3F65F4001011B8 /* capabilities.c in Sources */, + 435B54CB2A3F65F4001011B8 /* socketoptions.c in Sources */, + 435B54CC2A3F65F4001011B8 /* polling.c in Sources */, + 435B54CD2A3F65F4001011B8 /* receive.c in Sources */, + 435B54CE2A3F65F4001011B8 /* jniutil.c in Sources */, + 435B54CF2A3F65F4001011B8 /* init.c in Sources */, + 435B54D02A3F65F4001011B8 /* tipc.c in Sources */, + 435B54D12A3F65F4001011B8 /* config.c in Sources */, + 435B54D22A3F65F4001011B8 /* credentials.c in Sources */, + 4342342F2C91C2890046DBD9 /* stat_wrapper.c in Sources */, + 435B54D32A3F65F4001011B8 /* socketpair.c in Sources */, + 435B54D42A3F65F4001011B8 /* exceptions.c in Sources */, + 435B54D52A3F65F4001011B8 /* reflection.c in Sources */, + 435B54D62A3F65F4001011B8 /* listen.c in Sources */, + 435B54D72A3F65F4001011B8 /* ancillary.c in Sources */, + 435B54D82A3F65F4001011B8 /* accept.c in Sources */, + 438F4DAD2A6AF05000546E2C /* afsystem.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 435B54FD2A3F661F001011B8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 435B54FE2A3F661F001011B8 /* filedescriptors.c in Sources */, + 435B54FF2A3F661F001011B8 /* bind.c in Sources */, + 435B55002A3F661F001011B8 /* address.c in Sources */, + 435B55012A3F661F001011B8 /* vsock.c in Sources */, + 435B55022A3F661F001011B8 /* send.c in Sources */, + 435B55032A3F661F001011B8 /* socket.c in Sources */, + 435B55042A3F661F001011B8 /* pipe.c in Sources */, + 435B55052A3F661F001011B8 /* connect.c in Sources */, + 435B55062A3F661F001011B8 /* capabilities.c in Sources */, + 435B55072A3F661F001011B8 /* socketoptions.c in Sources */, + 435B55082A3F661F001011B8 /* polling.c in Sources */, + 435B55092A3F661F001011B8 /* receive.c in Sources */, + 435B550A2A3F661F001011B8 /* jniutil.c in Sources */, + 435B550B2A3F661F001011B8 /* init.c in Sources */, + 435B550C2A3F661F001011B8 /* tipc.c in Sources */, + 435B550D2A3F661F001011B8 /* config.c in Sources */, + 435B550E2A3F661F001011B8 /* credentials.c in Sources */, + 434234302C91C2890046DBD9 /* stat_wrapper.c in Sources */, + 435B550F2A3F661F001011B8 /* socketpair.c in Sources */, + 435B55102A3F661F001011B8 /* exceptions.c in Sources */, + 435B55112A3F661F001011B8 /* reflection.c in Sources */, + 435B55122A3F661F001011B8 /* listen.c in Sources */, + 435B55132A3F661F001011B8 /* ancillary.c in Sources */, + 435B55142A3F661F001011B8 /* accept.c in Sources */, + 438F4DAE2A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2921,12 +3875,14 @@ 436E167227FBB70B00A553EC /* tipc.c in Sources */, 430233F72650AB4E00823167 /* config.c in Sources */, 430235B32650AB4F00823167 /* credentials.c in Sources */, + 434234192C91C2890046DBD9 /* stat_wrapper.c in Sources */, 43B3DFB7266082000024822F /* socketpair.c in Sources */, 430235472650AB4F00823167 /* exceptions.c in Sources */, 430235232650AB4F00823167 /* reflection.c in Sources */, 430234AB2650AB4E00823167 /* listen.c in Sources */, 430234332650AB4E00823167 /* ancillary.c in Sources */, 430233DF2650AB4E00823167 /* accept.c in Sources */, + 438F4D972A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2951,12 +3907,14 @@ 43684DAA282EE24B005F2989 /* tipc.c in Sources */, 43684DAB282EE24B005F2989 /* config.c in Sources */, 43684DAC282EE24B005F2989 /* credentials.c in Sources */, + 434234272C91C2890046DBD9 /* stat_wrapper.c in Sources */, 43684DAD282EE24B005F2989 /* socketpair.c in Sources */, 43684DAE282EE24B005F2989 /* exceptions.c in Sources */, 43684DAF282EE24B005F2989 /* reflection.c in Sources */, 43684DB0282EE24B005F2989 /* listen.c in Sources */, 43684DB1282EE24B005F2989 /* ancillary.c in Sources */, 43684DB2282EE24B005F2989 /* accept.c in Sources */, + 438F4DA52A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2981,12 +3939,14 @@ 436B5AAA28244CB500574CD4 /* tipc.c in Sources */, 436B5AAB28244CB500574CD4 /* config.c in Sources */, 436B5AAC28244CB500574CD4 /* credentials.c in Sources */, + 434234202C91C2890046DBD9 /* stat_wrapper.c in Sources */, 436B5AAD28244CB500574CD4 /* socketpair.c in Sources */, 436B5AAE28244CB500574CD4 /* exceptions.c in Sources */, 436B5AAF28244CB500574CD4 /* reflection.c in Sources */, 436B5AB028244CB500574CD4 /* listen.c in Sources */, 436B5AB128244CB500574CD4 /* ancillary.c in Sources */, 436B5AB228244CB500574CD4 /* accept.c in Sources */, + 438F4D9E2A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3011,12 +3971,14 @@ 436B5AE42826F49A00574CD4 /* tipc.c in Sources */, 436B5AE52826F49A00574CD4 /* config.c in Sources */, 436B5AE62826F49A00574CD4 /* credentials.c in Sources */, + 434234212C91C2890046DBD9 /* stat_wrapper.c in Sources */, 436B5AE72826F49A00574CD4 /* socketpair.c in Sources */, 436B5AE82826F49A00574CD4 /* exceptions.c in Sources */, 436B5AE92826F49A00574CD4 /* reflection.c in Sources */, 436B5AEA2826F49A00574CD4 /* listen.c in Sources */, 436B5AEB2826F49A00574CD4 /* ancillary.c in Sources */, 436B5AEC2826F49A00574CD4 /* accept.c in Sources */, + 438F4D9F2A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3041,12 +4003,14 @@ 436E167F27FBB70B00A553EC /* tipc.c in Sources */, 43B3DFF826660A2D0024822F /* config.c in Sources */, 43B3DFF926660A2D0024822F /* credentials.c in Sources */, + 434234292C91C2890046DBD9 /* stat_wrapper.c in Sources */, 43B3DFFA26660A2D0024822F /* socketpair.c in Sources */, 43B3DFFB26660A2D0024822F /* exceptions.c in Sources */, 43B3DFFC26660A2D0024822F /* reflection.c in Sources */, 43B3DFFD26660A2D0024822F /* listen.c in Sources */, 43B3DFFE26660A2D0024822F /* ancillary.c in Sources */, 43B3DFFF26660A2D0024822F /* accept.c in Sources */, + 438F4DA72A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3071,12 +4035,14 @@ 436E168027FBB70B00A553EC /* tipc.c in Sources */, 43B3E03026672FD30024822F /* config.c in Sources */, 43B3E03126672FD30024822F /* credentials.c in Sources */, + 4342342A2C91C2890046DBD9 /* stat_wrapper.c in Sources */, 43B3E03226672FD30024822F /* socketpair.c in Sources */, 43B3E03326672FD30024822F /* exceptions.c in Sources */, 43B3E03426672FD30024822F /* reflection.c in Sources */, 43B3E03526672FD30024822F /* listen.c in Sources */, 43B3E03626672FD30024822F /* ancillary.c in Sources */, 43B3E03726672FD30024822F /* accept.c in Sources */, + 438F4DA82A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3101,12 +4067,14 @@ 436E167427FBB70B00A553EC /* tipc.c in Sources */, 43B3E068266756540024822F /* config.c in Sources */, 43B3E069266756540024822F /* credentials.c in Sources */, + 4342341B2C91C2890046DBD9 /* stat_wrapper.c in Sources */, 43B3E06A266756540024822F /* socketpair.c in Sources */, 43B3E06B266756540024822F /* exceptions.c in Sources */, 43B3E06C266756540024822F /* reflection.c in Sources */, 43B3E06D266756540024822F /* listen.c in Sources */, 43B3E06E266756540024822F /* ancillary.c in Sources */, 43B3E06F266756540024822F /* accept.c in Sources */, + 438F4D992A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3131,12 +4099,46 @@ 436E168127FBB70B00A553EC /* tipc.c in Sources */, 43B3E0A0266971630024822F /* config.c in Sources */, 43B3E0A1266971630024822F /* credentials.c in Sources */, + 4342342B2C91C2890046DBD9 /* stat_wrapper.c in Sources */, 43B3E0A2266971630024822F /* socketpair.c in Sources */, 43B3E0A3266971630024822F /* exceptions.c in Sources */, 43B3E0A4266971630024822F /* reflection.c in Sources */, 43B3E0A5266971630024822F /* listen.c in Sources */, 43B3E0A6266971630024822F /* ancillary.c in Sources */, 43B3E0A7266971630024822F /* accept.c in Sources */, + 438F4DA92A6AF05000546E2C /* afsystem.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 43C57CE22A38F370001DEA62 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 43C57CE32A38F370001DEA62 /* filedescriptors.c in Sources */, + 43C57CE42A38F370001DEA62 /* bind.c in Sources */, + 43C57CE52A38F370001DEA62 /* address.c in Sources */, + 43C57CE62A38F370001DEA62 /* vsock.c in Sources */, + 43C57CE72A38F370001DEA62 /* send.c in Sources */, + 43C57CE82A38F370001DEA62 /* socket.c in Sources */, + 43C57CE92A38F370001DEA62 /* pipe.c in Sources */, + 43C57CEA2A38F370001DEA62 /* connect.c in Sources */, + 43C57CEB2A38F370001DEA62 /* capabilities.c in Sources */, + 43C57CEC2A38F370001DEA62 /* socketoptions.c in Sources */, + 43C57CED2A38F370001DEA62 /* polling.c in Sources */, + 43C57CEE2A38F370001DEA62 /* receive.c in Sources */, + 43C57CEF2A38F370001DEA62 /* jniutil.c in Sources */, + 43C57CF02A38F370001DEA62 /* init.c in Sources */, + 43C57CF12A38F370001DEA62 /* tipc.c in Sources */, + 43C57CF22A38F370001DEA62 /* config.c in Sources */, + 43C57CF32A38F370001DEA62 /* credentials.c in Sources */, + 4342342D2C91C2890046DBD9 /* stat_wrapper.c in Sources */, + 43C57CF42A38F370001DEA62 /* socketpair.c in Sources */, + 43C57CF52A38F370001DEA62 /* exceptions.c in Sources */, + 43C57CF62A38F370001DEA62 /* reflection.c in Sources */, + 43C57CF72A38F370001DEA62 /* listen.c in Sources */, + 43C57CF82A38F370001DEA62 /* ancillary.c in Sources */, + 43C57CF92A38F370001DEA62 /* accept.c in Sources */, + 438F4DAB2A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3161,12 +4163,14 @@ 436E167527FBB70B00A553EC /* tipc.c in Sources */, 430233F92650AB4E00823167 /* config.c in Sources */, 430235B52650AB4F00823167 /* credentials.c in Sources */, + 4342341C2C91C2890046DBD9 /* stat_wrapper.c in Sources */, 43B3DFB9266082000024822F /* socketpair.c in Sources */, 430235492650AB4F00823167 /* exceptions.c in Sources */, 430235252650AB4F00823167 /* reflection.c in Sources */, 430234AD2650AB4E00823167 /* listen.c in Sources */, 430234352650AB4E00823167 /* ancillary.c in Sources */, 430233E12650AB4E00823167 /* accept.c in Sources */, + 438F4D9A2A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3191,12 +4195,14 @@ 436E167B27FBB70B00A553EC /* tipc.c in Sources */, 430233FF2650AB4E00823167 /* config.c in Sources */, 430235BB2650AB4F00823167 /* credentials.c in Sources */, + 434234242C91C2890046DBD9 /* stat_wrapper.c in Sources */, 43B3DFBF266082000024822F /* socketpair.c in Sources */, 4302354F2650AB4F00823167 /* exceptions.c in Sources */, 4302352B2650AB4F00823167 /* reflection.c in Sources */, 430234B32650AB4E00823167 /* listen.c in Sources */, 4302343B2650AB4E00823167 /* ancillary.c in Sources */, 430233E72650AB4E00823167 /* accept.c in Sources */, + 438F4DA22A6AF05000546E2C /* afsystem.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3208,6 +4214,11 @@ target = 43663ED6261AECC400CEED08 /* current */; targetProxy = 430235DA2650CCCD00823167 /* PBXContainerItemProxy */; }; + 430456D32C91B85A00CBAC42 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 430456952C91B81000CBAC42 /* loongarch64-openEuler-linux */; + targetProxy = 430456D22C91B85A00CBAC42 /* PBXContainerItemProxy */; + }; 4304A628266DFD24003BCEDF /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 4304A5F1266DFCEF003BCEDF /* x86_64-portbld-dragonfly6.0 */; @@ -3263,6 +4274,21 @@ target = 432EF1F7261ADEDD00F4A583 /* armv6--linux-gnueabihf */; targetProxy = 432EF204261AE01000F4A583 /* PBXContainerItemProxy */; }; + 435B54A32A3F65ED001011B8 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 435B54682A3F65D2001011B8 /* x86_64-linux-android30 */; + targetProxy = 435B54A22A3F65ED001011B8 /* PBXContainerItemProxy */; + }; + 435B54DF2A3F6617001011B8 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 435B54A42A3F65F4001011B8 /* i686-linux-android30 */; + targetProxy = 435B54DE2A3F6617001011B8 /* PBXContainerItemProxy */; + }; + 435B551B2A3F6658001011B8 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 435B54E02A3F661F001011B8 /* arm-linux-androideabi30 */; + targetProxy = 435B551A2A3F6658001011B8 /* PBXContainerItemProxy */; + }; 43684DB9282EE2CA005F2989 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 43684D80282EE24B005F2989 /* aarch64-w64-mingw32 */; @@ -3303,9 +4329,66 @@ target = 432EF206261AE29200F4A583 /* x86_64-apple-darwin18.2.0 */; targetProxy = 43D0E25B261AE48B008F2FFE /* PBXContainerItemProxy */; }; + 43EE6B472A3B3D0700E6F8F2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43C57CC52A38F370001DEA62 /* aarch64-linux-android30 */; + targetProxy = 43EE6B462A3B3D0700E6F8F2 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 430456CF2C91B81000CBAC42 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CROSSCLANG_TARGET = "loongarch64-openEuler-linux"; + EXECUTABLE_EXTENSION = so; + LD_DEPENDENCY_INFO_FILE = ""; + LD_LTO_OBJECT_FILE = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + OTHER_CFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-fPIC", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-fno-lto", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; + }; + name = Debug; + }; + 430456D02C91B81000CBAC42 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CROSSCLANG_TARGET = "loongarch64-openEuler-linux"; + EXECUTABLE_EXTENSION = so; + LD_DEPENDENCY_INFO_FILE = ""; + LD_LTO_OBJECT_FILE = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + OTHER_CFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-fPIC", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-fno-lto", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; + }; + name = Release; + }; 4304A624266DFCEF003BCEDF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -3314,7 +4397,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3331,7 +4414,7 @@ "-nostdlib", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Debug; }; @@ -3343,7 +4426,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3360,7 +4443,7 @@ "-nostdlib", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Release; }; @@ -3369,6 +4452,8 @@ buildSettings = { CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_STYLE = Automatic; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + OTHER_LDFLAGS = "-Xcrossclang-ignore-reproducible"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -3378,6 +4463,8 @@ buildSettings = { CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_STYLE = Automatic; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + OTHER_LDFLAGS = "-Xcrossclang-ignore-reproducible"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; @@ -3390,7 +4477,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3403,7 +4490,7 @@ "$(CROSSCLANG_TARGET)", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Debug; }; @@ -3415,7 +4502,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3428,7 +4515,7 @@ "$(CROSSCLANG_TARGET)", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Release; }; @@ -3440,7 +4527,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3448,7 +4535,7 @@ "-Xcrossclang-use-gcc=x86_64-w64-mingw32-gcc", ); OTHER_LDFLAGS = ( - "$(inheritedx)", + "$(inherited)", "-target", "$(CROSSCLANG_TARGET)", "-lws2_32", @@ -3458,7 +4545,8 @@ "-Xcrossclang-output-strip-lib-prefix", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; + USE_HEADERMAP = NO; }; name = Debug; }; @@ -3470,7 +4558,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3478,7 +4566,7 @@ "-Xcrossclang-use-gcc=x86_64-w64-mingw32-gcc", ); OTHER_LDFLAGS = ( - "$(inheritedx)", + "$(inherited)", "-target", "$(CROSSCLANG_TARGET)", "-lWs2_32", @@ -3488,7 +4576,8 @@ "-Xcrossclang-output-strip-lib-prefix", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; + USE_HEADERMAP = NO; }; name = Release; }; @@ -3500,7 +4589,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3515,7 +4604,7 @@ "-nostdlib", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Debug; }; @@ -3527,7 +4616,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3542,7 +4631,7 @@ "-nostdlib", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Release; }; @@ -3554,7 +4643,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3568,7 +4657,7 @@ "-fno-lto", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Debug; }; @@ -3580,7 +4669,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3594,7 +4683,7 @@ "-fno-lto", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Release; }; @@ -3606,7 +4695,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3619,7 +4708,7 @@ "$(CROSSCLANG_TARGET)", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Debug; }; @@ -3631,7 +4720,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3644,7 +4733,7 @@ "$(CROSSCLANG_TARGET)", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Release; }; @@ -3656,7 +4745,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3669,7 +4758,7 @@ "$(CROSSCLANG_TARGET)", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Debug; }; @@ -3681,7 +4770,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3694,7 +4783,7 @@ "$(CROSSCLANG_TARGET)", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Release; }; @@ -3706,7 +4795,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3723,7 +4812,7 @@ llvm, ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Debug; }; @@ -3735,7 +4824,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3752,7 +4841,7 @@ llvm, ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Release; }; @@ -3764,7 +4853,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3777,7 +4866,7 @@ "$(CROSSCLANG_TARGET)", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Debug; }; @@ -3789,7 +4878,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3802,7 +4891,7 @@ "$(CROSSCLANG_TARGET)", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Release; }; @@ -3810,12 +4899,13 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_IDENTITY = "-"; CROSSCLANG_TARGET = "x86_64-apple-darwin18.2.0"; EXECUTABLE_EXTENSION = dylib; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; LLVM_LTO = YES; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3826,7 +4916,7 @@ "-target", "$(CROSSCLANG_TARGET)", "-Xcrossclang-ld64-sdk-version", - "\"\"", + "\"undefined\"", ); PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; @@ -3837,12 +4927,13 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_IDENTITY = "-"; CROSSCLANG_TARGET = "x86_64-apple-darwin18.2.0"; EXECUTABLE_EXTENSION = dylib; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; LLVM_LTO = YES; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3853,22 +4944,185 @@ "-target", "$(CROSSCLANG_TARGET)", "-Xcrossclang-ld64-sdk-version", - "\"\"", + "\"undefined\"", ); PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; }; name = Release; }; + 435B549F2A3F65D2001011B8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CROSSCLANG_TARGET = "x86_64-linux-android30"; + EXECUTABLE_EXTENSION = so; + LD_DEPENDENCY_INFO_FILE = ""; + LD_LTO_OBJECT_FILE = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + OTHER_CFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-fPIC", + "-Wno-unknown-warning-option", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-Qunused-arguments", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; + }; + name = Debug; + }; + 435B54A02A3F65D2001011B8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CROSSCLANG_TARGET = "x86_64-linux-android30"; + EXECUTABLE_EXTENSION = so; + LD_DEPENDENCY_INFO_FILE = ""; + LD_LTO_OBJECT_FILE = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + OTHER_CFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-fPIC", + "-Wno-unknown-warning-option", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-Qunused-arguments", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; + }; + name = Release; + }; + 435B54DB2A3F65F4001011B8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CROSSCLANG_TARGET = "i686-linux-android30"; + EXECUTABLE_EXTENSION = so; + LD_DEPENDENCY_INFO_FILE = ""; + LD_LTO_OBJECT_FILE = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + OTHER_CFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-fPIC", + "-Wno-unknown-warning-option", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-Qunused-arguments", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; + }; + name = Debug; + }; + 435B54DC2A3F65F4001011B8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CROSSCLANG_TARGET = "i686-linux-android30"; + EXECUTABLE_EXTENSION = so; + LD_DEPENDENCY_INFO_FILE = ""; + LD_LTO_OBJECT_FILE = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + OTHER_CFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-fPIC", + "-Wno-unknown-warning-option", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-Qunused-arguments", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; + }; + name = Release; + }; + 435B55172A3F661F001011B8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CROSSCLANG_TARGET = "arm-linux-androideabi30"; + EXECUTABLE_EXTENSION = so; + LD_DEPENDENCY_INFO_FILE = ""; + LD_LTO_OBJECT_FILE = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + OTHER_CFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-fPIC", + "-Wno-unknown-warning-option", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-Qunused-arguments", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; + }; + name = Debug; + }; + 435B55182A3F661F001011B8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CROSSCLANG_TARGET = "arm-linux-androideabi30"; + EXECUTABLE_EXTENSION = so; + LD_DEPENDENCY_INFO_FILE = ""; + LD_LTO_OBJECT_FILE = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + OTHER_CFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-fPIC", + "-Wno-unknown-warning-option", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-Qunused-arguments", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; + }; + name = Release; + }; 43663EE0261AECC400CEED08 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_IDENTITY = "-"; CROSSCLANG_TARGET = current; EXECUTABLE_EXTENSION = dylib; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3888,11 +5142,12 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_IDENTITY = "-"; CROSSCLANG_TARGET = current; EXECUTABLE_EXTENSION = dylib; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3916,7 +5171,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3924,7 +5179,7 @@ "-Xcrossclang-use-gcc=aarch64-w64-mingw32-gcc", ); OTHER_LDFLAGS = ( - "$(inheritedx)", + "$(inherited)", "-target", "$(CROSSCLANG_TARGET)", "-lws2_32", @@ -3935,7 +5190,8 @@ "-Xcrossclang-ignore-export-dynamic", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; + USE_HEADERMAP = NO; }; name = Debug; }; @@ -3947,7 +5203,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -3955,7 +5211,7 @@ "-Xcrossclang-use-gcc=aarch64-w64-mingw32-gcc", ); OTHER_LDFLAGS = ( - "$(inheritedx)", + "$(inherited)", "-target", "$(CROSSCLANG_TARGET)", "-lWs2_32", @@ -3966,7 +5222,8 @@ "-Xcrossclang-ignore-export-dynamic", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; + USE_HEADERMAP = NO; }; name = Release; }; @@ -3983,7 +5240,7 @@ ); LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -4003,7 +5260,8 @@ ); PRODUCT_NAME = "$(TARGET_NAME)"; RUN_CLANG_STATIC_ANALYZER = NO; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; + USE_HEADERMAP = NO; }; name = Debug; }; @@ -4019,7 +5277,7 @@ ); LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -4039,7 +5297,8 @@ ); PRODUCT_NAME = "$(TARGET_NAME)"; RUN_CLANG_STATIC_ANALYZER = NO; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; + USE_HEADERMAP = NO; }; name = Release; }; @@ -4057,7 +5316,7 @@ ); LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -4077,7 +5336,8 @@ ); PRODUCT_NAME = "$(TARGET_NAME)"; RUN_CLANG_STATIC_ANALYZER = NO; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; + USE_HEADERMAP = NO; }; name = Debug; }; @@ -4094,7 +5354,7 @@ ); LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -4114,7 +5374,8 @@ ); PRODUCT_NAME = "$(TARGET_NAME)"; RUN_CLANG_STATIC_ANALYZER = NO; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; + USE_HEADERMAP = NO; }; name = Release; }; @@ -4126,7 +5387,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -4140,7 +5401,7 @@ "-Wl,--no-rosegment,--disable-new-dtags,-znognustack,-znoseparate-code,-znorelro", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Debug; }; @@ -4152,7 +5413,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -4166,7 +5427,7 @@ "-Wl,--no-rosegment,--disable-new-dtags,-znognustack,-znoseparate-code,-znorelro", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Release; }; @@ -4178,7 +5439,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -4191,7 +5452,7 @@ "$(CROSSCLANG_TARGET)", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Debug; }; @@ -4203,7 +5464,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -4216,7 +5477,7 @@ "$(CROSSCLANG_TARGET)", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Release; }; @@ -4228,7 +5489,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -4242,7 +5503,7 @@ "-Wl,--no-rosegment,--disable-new-dtags", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Debug; }; @@ -4254,7 +5515,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -4268,7 +5529,7 @@ "-Wl,--no-rosegment,--disable-new-dtags", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Release; }; @@ -4281,7 +5542,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-fno-lto", @@ -4301,7 +5562,7 @@ "-lrt", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Debug; }; @@ -4314,7 +5575,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-fno-lto", @@ -4334,7 +5595,61 @@ "-lrt", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; + }; + name = Release; + }; + 43C57CFC2A38F370001DEA62 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CROSSCLANG_TARGET = "aarch64-linux-android30"; + EXECUTABLE_EXTENSION = so; + LD_DEPENDENCY_INFO_FILE = ""; + LD_LTO_OBJECT_FILE = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + OTHER_CFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-fPIC", + "-Wno-unknown-warning-option", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-Qunused-arguments", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; + }; + name = Debug; + }; + 43C57CFD2A38F370001DEA62 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CROSSCLANG_TARGET = "aarch64-linux-android30"; + EXECUTABLE_EXTENSION = so; + LD_DEPENDENCY_INFO_FILE = ""; + LD_LTO_OBJECT_FILE = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + OTHER_CFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-fPIC", + "-Wno-unknown-warning-option", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-target", + "$(CROSSCLANG_TARGET)", + "-Qunused-arguments", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; }; name = Release; }; @@ -4342,11 +5657,12 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_IDENTITY = "-"; CROSSCLANG_TARGET = "arm64-apple-macos11"; EXECUTABLE_EXTENSION = dylib; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -4366,11 +5682,12 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_IDENTITY = "-"; CROSSCLANG_TARGET = "arm64-apple-macos11"; EXECUTABLE_EXTENSION = dylib; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -4414,8 +5731,11 @@ CODE_SIGN_STYLE = Manual; COMPILER_INDEX_STORE_ENABLE = NO; COPY_PHASE_STRIP = NO; + CROSSCLANG_SDKROOT = "crossclang/Xcode-Support/crossclang.sdk"; + DEAD_CODE_STRIPPING = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; EXECUTABLE_PREFIX = "libjunixsocket-"; GCC_CW_ASM_SYNTAX = NO; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -4429,7 +5749,9 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INTERMEDIATE_TEXT_BASED_STUBS = NO; HEADER_SEARCH_PATHS = src/main/c/jni; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = ( "-fno-strict-overflow", @@ -4441,7 +5763,10 @@ "-fno-common", "-fvisibility=hidden", ); - OTHER_LDFLAGS = ""; + OTHER_LDFLAGS = ( + "-Xcrossclang-ignore-reproducible", + "-Wno-unused-command-line-argument", + ); RUN_CLANG_STATIC_ANALYZER = YES; SKIP_INSTALL = YES; WARNING_CFLAGS = ( @@ -4463,6 +5788,7 @@ "-Wno-unused-command-line-argument", "-Werror-implicit-function-declaration", "-Wno-declaration-after-statement", + "-Wno-unsafe-buffer-usage", ); }; name = Debug; @@ -4495,7 +5821,10 @@ CODE_SIGN_STYLE = Manual; COMPILER_INDEX_STORE_ENABLE = NO; COPY_PHASE_STRIP = NO; + CROSSCLANG_SDKROOT = "crossclang/Xcode-Support/crossclang.sdk"; + DEAD_CODE_STRIPPING = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; EXECUTABLE_PREFIX = "libjunixsocket-"; GCC_CW_ASM_SYNTAX = NO; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -4508,7 +5837,9 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INTERMEDIATE_TEXT_BASED_STUBS = NO; HEADER_SEARCH_PATHS = src/main/c/jni; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = ( "-fno-strict-overflow", @@ -4520,7 +5851,10 @@ "-fno-common", "-fvisibility=hidden", ); - OTHER_LDFLAGS = ""; + OTHER_LDFLAGS = ( + "-Xcrossclang-ignore-reproducible", + "-Wno-unused-command-line-argument", + ); RUN_CLANG_STATIC_ANALYZER = YES; SKIP_INSTALL = YES; WARNING_CFLAGS = ( @@ -4542,6 +5876,7 @@ "-Wno-unused-command-line-argument", "-Werror-implicit-function-declaration", "-Wno-declaration-after-statement", + "-Wno-unsafe-buffer-usage", ); }; name = Release; @@ -4554,7 +5889,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -4569,7 +5904,7 @@ "-nostdlib", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; SYSTEM_HEADER_SEARCH_PATHS = ( "$(inherited)", "$(SDKROOT)$(CROSSCLANGROOT)/usr/include", @@ -4586,7 +5921,7 @@ EXECUTABLE_EXTENSION = so; LD_DEPENDENCY_INFO_FILE = ""; LD_LTO_OBJECT_FILE = ""; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_CFLAGS = ( "$(inherited)", "-target", @@ -4601,7 +5936,7 @@ "-nostdlib", ); PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = crossclang.macosx1.0; + SDKROOT = "$(CROSSCLANG_SDKROOT)"; SYSTEM_HEADER_SEARCH_PATHS = ( "$(inherited)", "$(SDKROOT)$(CROSSCLANGROOT)/usr/include", @@ -4613,6 +5948,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 430456CE2C91B81000CBAC42 /* Build configuration list for PBXNativeTarget "loongarch64-openEuler-linux" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 430456CF2C91B81000CBAC42 /* Debug */, + 430456D02C91B81000CBAC42 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 4304A623266DFCEF003BCEDF /* Build configuration list for PBXNativeTarget "x86_64-portbld-dragonfly6.0" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -4712,6 +6056,33 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 435B549E2A3F65D2001011B8 /* Build configuration list for PBXNativeTarget "x86_64-linux-android30" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 435B549F2A3F65D2001011B8 /* Debug */, + 435B54A02A3F65D2001011B8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 435B54DA2A3F65F4001011B8 /* Build configuration list for PBXNativeTarget "i686-linux-android30" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 435B54DB2A3F65F4001011B8 /* Debug */, + 435B54DC2A3F65F4001011B8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 435B55162A3F661F001011B8 /* Build configuration list for PBXNativeTarget "arm-linux-androideabi30" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 435B55172A3F661F001011B8 /* Debug */, + 435B55182A3F661F001011B8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 43663EDF261AECC400CEED08 /* Build configuration list for PBXNativeTarget "current" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -4784,6 +6155,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 43C57CFB2A38F370001DEA62 /* Build configuration list for PBXNativeTarget "aarch64-linux-android30" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 43C57CFC2A38F370001DEA62 /* Debug */, + 43C57CFD2A38F370001DEA62 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 43C5FB7A261AC3700088E301 /* Build configuration list for PBXNativeTarget "arm64-apple-macos11" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/All Architectures.xcscheme b/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/All Architectures.xcscheme index 9f879e63b..d02a8f2a0 100644 --- a/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/All Architectures.xcscheme +++ b/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/All Architectures.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/aarch64-linux-gnu.xcscheme b/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/aarch64-linux-gnu.xcscheme index 22e93454e..4f65dcd8f 100644 --- a/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/aarch64-linux-gnu.xcscheme +++ b/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/aarch64-linux-gnu.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/arm64-apple-macos11.xcscheme b/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/arm64-apple-macos11.xcscheme index 868ef8c02..25fa398a9 100644 --- a/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/arm64-apple-macos11.xcscheme +++ b/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/arm64-apple-macos11.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/loongarch64-openEuler-linux.xcscheme b/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/loongarch64-openEuler-linux.xcscheme new file mode 100644 index 000000000..4cff200aa --- /dev/null +++ b/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/loongarch64-openEuler-linux.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/powerpc-ibm-aix7.2.xcscheme b/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/powerpc-ibm-aix7.2.xcscheme index b3d78f413..4f975ac9b 100644 --- a/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/powerpc-ibm-aix7.2.xcscheme +++ b/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/powerpc-ibm-aix7.2.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/x86_64-portbld-dragonfly6.0.xcscheme b/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/x86_64-portbld-dragonfly6.0.xcscheme index 3ec1048a4..27313ba47 100644 --- a/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/x86_64-portbld-dragonfly6.0.xcscheme +++ b/junixsocket-native/junixsocket-native.xcodeproj/xcshareddata/xcschemes/x86_64-portbld-dragonfly6.0.xcscheme @@ -1,6 +1,6 @@ com.kohlschutter.junixsocket junixsocket - 2.6.1 + 2.10.1 ../pom.xml junixsocket-native @@ -28,7 +28,7 @@ ${junixsocket.native.default.options} - + ${junixsocket.native.default.c.options} ${junixsocket.native.default.cpp.options} ${junixsocket.native.aol} @@ -61,6 +61,12 @@ cross + + + cross + true + + @@ -83,6 +89,11 @@ llvm + + + !crossclang.disable + + @@ -123,6 +134,11 @@ codeql-skip-c + + + codeql-skip-c + + @@ -144,6 +160,7 @@ codeql-nar-compile + compile nar-compile @@ -164,6 +181,71 @@ + + fix-jnilib + + + !m2e.version + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + fix-jnilib + prepare-package + + + + + + + + + + + + + + + + + + + + + + + + + + { +"resources":{ +"includes":[ +{ +"pattern":"^lib/${junixsocket.native.aol.llvm}/jni/.*" +} +]}, +"bundles":[] +} + + + + + + + + run + + + + + + + @@ -215,61 +297,6 @@ - - org.apache.maven.plugins - maven-antrun-plugin - - - fix-jnilib - prepare-package - - - - - - - - - - - - - - - - - - - - - - - - - - - { - "resources":{ - "includes":[ - { - "pattern":"^lib/${junixsocket.native.aol.llvm}/jni/.*" - } - ]}, - "bundles":[] -} - - - - - - - - run - - - - org.apache.maven.plugins maven-install-plugin diff --git a/junixsocket-native/src/main/c/.gitignore b/junixsocket-native/src/main/c/.gitignore new file mode 100644 index 000000000..1854c34c1 --- /dev/null +++ b/junixsocket-native/src/main/c/.gitignore @@ -0,0 +1,8 @@ +# Ignore assembled libraries +*.so +*.dylib +*.dll + +# Ignore assembly code +*.a +*.lst diff --git a/junixsocket-native/src/main/c/accept.c b/junixsocket-native/src/main/c/accept.c index 3874341c1..cf145202c 100644 --- a/junixsocket-native/src/main/c/accept.c +++ b/junixsocket-native/src/main/c/accept.c @@ -72,7 +72,7 @@ JNIEXPORT jboolean JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_accept int serverHandle = _getFD(env, fdServer); if(serverHandle < 0) { - _throwException(env, kExceptionSocketException, "Socket is closed"); + _throwException(env, kExceptionSocketClosedException, "Socket is closed"); return false; } diff --git a/junixsocket-native/src/main/c/address.c b/junixsocket-native/src/main/c/address.c index 161918a85..e2db9652a 100644 --- a/junixsocket-native/src/main/c/address.c +++ b/junixsocket-native/src/main/c/address.c @@ -28,7 +28,7 @@ // Either we have sun_len (and we can skip the null byte), or we add the null byte at the end static const socklen_t SUN_NAME_MAX_LEN = (socklen_t)(sizeof(struct sockaddr_un) - 2); -struct __attribute__((__packed__)) jux_tipc_addr { +struct CK_STRUCT_PACKED jux_tipc_addr { jint addrType; jint scope; jint a; @@ -36,12 +36,23 @@ struct __attribute__((__packed__)) jux_tipc_addr { jint c; }; -struct __attribute__((__packed__)) jux_vsock_addr { +struct CK_STRUCT_PACKED jux_vsock_addr { jint reserved1; jint port; jint cid; }; +struct CK_STRUCT_PACKED jux_system_addr { + jint sysaddr; + jint id; + jint unit; + jint reserved0; + jint reserved1; + jint reserved2; + jint reserved3; + jint reserved4; +}; + int domainToNative(int domain) { switch(domain) { case org_newsclub_net_unix_NativeUnixSocket_DOMAIN_UNIX: @@ -53,6 +64,10 @@ int domainToNative(int domain) { #if junixsocket_have_vsock case org_newsclub_net_unix_NativeUnixSocket_DOMAIN_VSOCK: return AF_VSOCK; +#endif +#if junixsocket_have_system + case org_newsclub_net_unix_NativeUnixSocket_DOMAIN_SYSTEM: + return AF_SYSTEM; #endif default: // do not throw: _throwException(env, kExceptionSocketException, "Unsupported domain"); @@ -131,6 +146,10 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_sockAddrLengt #if junixsocket_have_vsock case AF_VSOCK: return sizeof(struct sockaddr_vm); +#endif +#if junixsocket_have_system + case AF_SYSTEM: + return sizeof(struct sockaddr_ctl); #endif default: _throwException(env, kExceptionSocketException, "Unsupported domain"); @@ -138,10 +157,27 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_sockAddrLengt } } -static jbyteArray sockAddrUnToBytes(JNIEnv *env, struct sockaddr_un *addr, socklen_t len) { +static jbyteArray sockAddrGenericToBytes(JNIEnv *env, jux_sockaddr_t *addr, socklen_t len) { + if(len <= 0 || addr == NULL) { + return NULL; + } +#if defined(junixsocket_have_sun_len) + len = MIN(len, addr->addr.sa_len); if(len <= 0) { return NULL; } +#endif + + jbyteArray array = (*env)->NewByteArray(env, len); + (*env)->SetByteArrayRegion(env, array, 0, len, (jbyte*)addr); + + return array; +} + +static jbyteArray sockAddrUnToBytes(JNIEnv *env, struct sockaddr_un *addr, socklen_t len) { + if(len <= 0 || addr == NULL) { + return NULL; + } #if defined(junixsocket_have_sun_len) len = MIN(len, sizeof(struct sockaddr_un) - 2); if(len < 256 && addr->sun_len < len) { @@ -224,6 +260,29 @@ static jbyteArray sockAddrVsockToBytes(JNIEnv *env, struct sockaddr_vm *addr) { #endif +#if junixsocket_have_system + +static jbyteArray sockAddrSystemToBytes(JNIEnv *env, struct sockaddr_ctl *addr) { + CK_IGNORE_RESERVED_IDENTIFIER_BEGIN // htonl + struct jux_system_addr buf = { + .sysaddr = htonl(addr->ss_sysaddr), + .id = htonl(addr->sc_id), + .unit = htonl(addr->sc_unit), + .reserved0 = htonl(addr->sc_reserved[0]), + .reserved1 = htonl(addr->sc_reserved[1]), + .reserved2 = htonl(addr->sc_reserved[2]), + .reserved3 = htonl(addr->sc_reserved[3]), + .reserved4 = htonl(addr->sc_reserved[4]) + }; + CK_IGNORE_RESERVED_IDENTIFIER_END + + jbyteArray array = (*env)->NewByteArray(env, sizeof(typeof(buf))); + (*env)->SetByteArrayRegion(env, array, 0, sizeof(typeof(buf)), (jbyte*)&buf); + return array; +} + +#endif + /* * Class: org_newsclub_net_unix_NativeUnixSocket * Method: sockname @@ -233,10 +292,12 @@ JNIEXPORT jbyteArray JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_socknam (JNIEnv * env, jclass clazz CK_UNUSED, jint domain, jobject fd, jboolean peerName) { int handle = _getFD(env, fd); - domain = domainToNative(domain); - if(domain == -1) { - _throwException(env, kExceptionSocketException, "Unsupported domain"); - return NULL; + if(domain != org_newsclub_net_unix_NativeUnixSocket_DOMAIN_GENERIC) { + domain = domainToNative(domain); + if(domain == -1) { + _throwException(env, kExceptionSocketException, "Unsupported domain"); + return NULL; + } } jux_sockaddr_t addr = {0}; @@ -276,6 +337,10 @@ JNIEXPORT jbyteArray JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_socknam return NULL; } + if(domain == org_newsclub_net_unix_NativeUnixSocket_DOMAIN_GENERIC) { + return sockAddrGenericToBytes(env, &addr, len); + } + if(addr.addr.sa_family != domain) { #if defined(_WIN32) if(addr.addr.sa_family == AF_INET && domain == AF_UNIX) { @@ -327,6 +392,15 @@ JNIEXPORT jbyteArray JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_socknam return NULL; } return sockAddrVsockToBytes(env, &addr.vsock); +#endif +#if junixsocket_have_system + case AF_SYSTEM: + if(len > (socklen_t)sizeof(struct sockaddr_ctl)) { + _throwException(env, kExceptionSocketException, + peerName ? "peer sockname too long" : "sockname too long"); + return NULL; + } + return sockAddrSystemToBytes(env, &addr.system); #endif default: _throwException(env, kExceptionSocketException, @@ -357,6 +431,11 @@ JNIEXPORT jbyteArray JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_sockAdd case AF_VSOCK: sockAddrLen = sizeof(struct sockaddr_vm); break; +#endif +#if junixsocket_have_system + case AF_SYSTEM: + sockAddrLen = sizeof(struct sockaddr_ctl); + break; #endif default: _throwException(env, kExceptionSocketException, "Unsupported domain"); @@ -396,6 +475,10 @@ JNIEXPORT jbyteArray JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_sockAdd #if junixsocket_have_vsock case AF_VSOCK: return sockAddrVsockToBytes(env, (struct sockaddr_vm *)addr); +#endif +#if junixsocket_have_system + case AF_SYSTEM: + return sockAddrSystemToBytes(env, (struct sockaddr_ctl *)addr); #endif default: _throwException(env, kExceptionSocketException, "Unsupported domain"); @@ -412,8 +495,19 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_bytesToSockAd (JNIEnv *env, jclass clazz CK_UNUSED, jint domain, jobject directByteBuf, jbyteArray addressBytes) { size_t sockAddrLen; - domain = domainToNative(domain); + if(domain != org_newsclub_net_unix_NativeUnixSocket_DOMAIN_GENERIC) { + domain = domainToNative(domain); + if(domain == -1) { + return -1; + } + } + + jsize len = addressBytes == NULL ? 0 : (*env)->GetArrayLength(env, addressBytes); + switch(domain) { + case org_newsclub_net_unix_NativeUnixSocket_DOMAIN_GENERIC: // Generic + sockAddrLen = len; + break; case AF_UNIX: sockAddrLen = sizeof(struct sockaddr_un); break; @@ -426,6 +520,11 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_bytesToSockAd case AF_VSOCK: sockAddrLen = sizeof(struct sockaddr_vm); break; +#endif +#if junixsocket_have_system + case AF_SYSTEM: + sockAddrLen = sizeof(struct sockaddr_ctl); + break; #endif default: // do not throw: _throwException(env, kExceptionSocketException, "Unsupported domain"); @@ -439,7 +538,6 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_bytesToSockAd return -1; } - jsize len = addressBytes == NULL ? 0 : (*env)->GetArrayLength(env, addressBytes); if(len > directByteBufRef.size || len > INT_MAX) { _throwException(env, kExceptionSocketException, "Byte array is too large"); return -1; @@ -454,6 +552,12 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_bytesToSockAd } switch(domain) { + case org_newsclub_net_unix_NativeUnixSocket_DOMAIN_GENERIC: + { + (*env)->GetByteArrayRegion(env, addressBytes, 0, len, (signed char*)addr); + sockAddrLen = len; + } + break; case AF_UNIX: { struct sockaddr_un *addrUn = (struct sockaddr_un *) addr; @@ -503,6 +607,28 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_bytesToSockAd } break; #endif +#if junixsocket_have_system + case AF_SYSTEM: + { + struct jux_system_addr jaddr = {0}; + (*env)->GetByteArrayRegion(env, addressBytes, 0, sizeof(struct jux_system_addr), (void*)&jaddr); + + CK_IGNORE_RESERVED_IDENTIFIER_BEGIN // ntohl + addr->system.sc_len = sizeof(struct sockaddr_ctl); + addr->system.ss_sysaddr = (jshort)ntohl(jaddr.sysaddr); + addr->system.sc_id = ntohl(jaddr.id); + addr->system.sc_unit = ntohl(jaddr.unit); + addr->system.sc_reserved[0] = ntohl(jaddr.reserved0); + addr->system.sc_reserved[1] = ntohl(jaddr.reserved1); + addr->system.sc_reserved[2] = ntohl(jaddr.reserved2); + addr->system.sc_reserved[3] = ntohl(jaddr.reserved3); + addr->system.sc_reserved[4] = ntohl(jaddr.reserved4); + CK_IGNORE_RESERVED_IDENTIFIER_END + } + break; +#endif + default: + return -1; // unexpected; unsupported } return (jint)sockAddrLen; @@ -552,7 +678,7 @@ bool fixupSocketAddressPostError(int handle, jux_sockaddr_t *sa, socklen_t addrL CK_ARGUMENT_POTENTIALLY_UNUSED(errnum); #if defined(__linux__) && junixsocket_have_vsock - if(sa != NULL && addrLen >= sizeof(struct sockaddr_vm) && sa->addr.sa_family == AF_VSOCK) { + if(sa != NULL && addrLen >= (socklen_t)sizeof(struct sockaddr_vm) && sa->addr.sa_family == AF_VSOCK) { switch(errnum) { case EINVAL: case EADDRNOTAVAIL: diff --git a/junixsocket-native/src/main/c/address.h b/junixsocket-native/src/main/c/address.h index 7593f3ab4..1eef06e60 100644 --- a/junixsocket-native/src/main/c/address.h +++ b/junixsocket-native/src/main/c/address.h @@ -29,6 +29,9 @@ typedef union { #endif #if junixsocket_have_vsock struct sockaddr_vm vsock; +#endif +#if junixsocket_have_system + struct sockaddr_ctl system; #endif char bytes[128]; } jux_sockaddr_t; diff --git a/junixsocket-native/src/main/c/afsystem.c b/junixsocket-native/src/main/c/afsystem.c new file mode 100644 index 000000000..13578e6ab --- /dev/null +++ b/junixsocket-native/src/main/c/afsystem.c @@ -0,0 +1,76 @@ +/* + * junixsocket + * + * Copyright 2009-2023 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "afsystem.h" + +#include "exceptions.h" +#include "filedescriptors.h" + +#if junixsocket_have_system + +static inline char* java_to_char(JNIEnv* env, jstring string) { + jsize len = (*env)->GetStringLength(env, string); + size_t bytes = (*env)->GetStringUTFLength(env, string); + char* chars = (char*) malloc(bytes + 1); + (*env)->GetStringUTFRegion(env, string, 0, len, chars); + chars[bytes] = 0; + return chars; +} + +#endif + +/* + * Class: org_newsclub_net_unix_NativeUnixSocket + * Method: systemResolveCtlId + * Signature: (Ljava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_systemResolveCtlId +(JNIEnv *env, jclass klazz CK_UNUSED, jobject fd, jstring ctlName) { + CK_ARGUMENT_POTENTIALLY_UNUSED(env); + CK_ARGUMENT_POTENTIALLY_UNUSED(fd); + CK_ARGUMENT_POTENTIALLY_UNUSED(ctlName); + +#if junixsocket_have_system + + struct ctl_info info = {0}; + + char *name = java_to_char(env, ctlName); + + size_t nameLen = strlen(name); + + strlcpy(info.ctl_name, name, MIN(nameLen + 1, sizeof(info.ctl_name))); + + free(name); + +// (*env)->ReleaseStringUTFChars(env, ctlName, name); + + int fdHandle = _getFD(env, fd); + + if(ioctl(fdHandle, CTLIOCGINFO, &info) != 0) { + _throwErrnumException(env, errno, NULL); + return -1; + } + + return info.ctl_id; + +#else + _throwException(env, kExceptionSocketException, "AF_SYSTEM is not supported"); + return -1; +#endif +} + diff --git a/junixsocket-native/src/main/c/afsystem.h b/junixsocket-native/src/main/c/afsystem.h new file mode 100644 index 000000000..97b2052b8 --- /dev/null +++ b/junixsocket-native/src/main/c/afsystem.h @@ -0,0 +1,24 @@ +/* + * junixsocket + * + * Copyright 2009-2023 Christian Kohlschütter + * + * Licensed 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef afsystem_h +#define afsystem_h + +#include "config.h" + +#endif /* afsystem_h */ diff --git a/junixsocket-native/src/main/c/bind.c b/junixsocket-native/src/main/c/bind.c index 4f94c28dc..abde64390 100644 --- a/junixsocket-native/src/main/c/bind.c +++ b/junixsocket-native/src/main/c/bind.c @@ -215,7 +215,8 @@ JNIEXPORT jlong JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_bind #endif int bindRes; - if(attempt == 0 && !reuse && addr->addr.sa_family == AF_UNIX) { + if(attempt == 0 && !reuse && addr->addr.sa_family == AF_UNIX && + (options & org_newsclub_net_unix_NativeUnixSocket_OPT_DGRAM_MODE) == 0) { // if we're not going to reuse the socket, let's try to connect first. // This avoids changing file metadata (e.g. ctime!) bindRes = -1; diff --git a/junixsocket-native/src/main/c/capabilities.c b/junixsocket-native/src/main/c/capabilities.c index a94b1347d..8230930b7 100644 --- a/junixsocket-native/src/main/c/capabilities.c +++ b/junixsocket-native/src/main/c/capabilities.c @@ -19,23 +19,32 @@ #include "config.h" #include "capabilities.h" +#if defined(__linux__) + #include + #include +#endif + #include "filedescriptors.h" #include "init.h" +#include "reflection.h" CK_IGNORE_UNUSED_VARIABLE_BEGIN // see AFSocketCapability.java in junixsocket-common -static int CAPABILITY_PEER_CREDENTIALS = (1 << 0); -static int CAPABILITY_ANCILLARY_MESSAGES = (1 << 1); -static int CAPABILITY_FILE_DESCRIPTORS = (1 << 2); -static int CAPABILITY_ABSTRACT_NAMESPACE = (1 << 3); -static int CAPABILITY_UNIX_DATAGRAMS = (1 << 4); -static int CAPABILITY_NATIVE_SOCKETPAIR = (1 << 5); -static int CAPABILITY_FD_AS_REDIRECT = (1 << 6); -static int CAPABILITY_TIPC = (1 << 7); -static int CAPABILITY_UNIX_DOMAIN = (1 << 8); -static int CAPABILITY_VSOCK = (1 << 9); -static int CAPABILITY_VSOCK_DGRAM = (1 << 10); -static int CAPABILITY_ZERO_LENGTH_SEND = (1 << 11); +static jint CAPABILITY_PEER_CREDENTIALS = (1 << 0); +static jint CAPABILITY_ANCILLARY_MESSAGES = (1 << 1); +static jint CAPABILITY_FILE_DESCRIPTORS = (1 << 2); +static jint CAPABILITY_ABSTRACT_NAMESPACE = (1 << 3); +static jint CAPABILITY_UNIX_DATAGRAMS = (1 << 4); +static jint CAPABILITY_NATIVE_SOCKETPAIR = (1 << 5); +static jint CAPABILITY_FD_AS_REDIRECT = (1 << 6); +static jint CAPABILITY_TIPC = (1 << 7); +static jint CAPABILITY_UNIX_DOMAIN = (1 << 8); +static jint CAPABILITY_VSOCK = (1 << 9); +static jint CAPABILITY_VSOCK_DGRAM = (1 << 10); +static jint CAPABILITY_ZERO_LENGTH_SEND = (1 << 11); +static jint CAPABILITY_UNSAFE = (1 << 12); +static jint CAPABILITY_LARGE_PORTS = (1 << 13); +static jint CAPABILITY_DARWIN = (1 << 14); CK_IGNORE_UNUSED_VARIABLE_END void init_capabilities(JNIEnv *env CK_UNUSED) { @@ -44,6 +53,30 @@ void init_capabilities(JNIEnv *env CK_UNUSED) { void destroy_capabilities(JNIEnv *env CK_UNUSED) { } +#if defined(__linux__) +static bool checkHasAbstractNamespace(void) { + bool hasAbstractNamespace = true; + + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + .sun_path = "\0junixsocket-capability-abstract\0" + }; + + int sockFd = socket(AF_UNIX, SOCK_STREAM, 0); + if(sockFd != -1) { + if(bind(sockFd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) { + // typically, ENOENT is returned on NetBSD/FreeBSD + if(errno != EADDRINUSE) { + hasAbstractNamespace = false; + } + } + close(sockFd); + } + + return hasAbstractNamespace; +} +#endif + /* * Class: org_newsclub_net_unix_NativeUnixSocket * Method: capabilities @@ -52,7 +85,11 @@ void destroy_capabilities(JNIEnv *env CK_UNUSED) { JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_capabilities( JNIEnv *env CK_UNUSED, jclass clazz CK_UNUSED) { - int capabilities = 0; + jint capabilities = 0; + +#if !defined(_WIN32) + capabilities |= CAPABILITY_UNSAFE; +#endif if(supportsUNIX()) { capabilities |= CAPABILITY_UNIX_DOMAIN; @@ -75,7 +112,12 @@ defined(SO_PEERCRED) || defined(SO_PEERID) || defined(__NetBSD__) || defined(__s // despite earlier claims [1], it's not supported in Windows 10 (yet) [2] // [1] https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ // [2] https://github.com/microsoft/WSL/issues/4240 - capabilities |= CAPABILITY_ABSTRACT_NAMESPACE; + + // Some Linux emulation layers (like NetBSD, FreeBSD) don't support + // the abstract socket namespace + if(checkHasAbstractNamespace()) { + capabilities |= CAPABILITY_ABSTRACT_NAMESPACE; + } #endif #if !defined(_WIN32) @@ -108,5 +150,12 @@ defined(SO_PEERCRED) || defined(SO_PEERID) || defined(__NetBSD__) || defined(__s capabilities |= CAPABILITY_ZERO_LENGTH_SEND; } + if(supportsLargePorts()) { + capabilities |= CAPABILITY_LARGE_PORTS; + } + +#if junixsocket_have_system + capabilities |= CAPABILITY_DARWIN; +#endif return capabilities; } diff --git a/junixsocket-native/src/main/c/ckmacros.h b/junixsocket-native/src/main/c/ckmacros.h index b5af91ba0..ab2aa5ed3 100644 --- a/junixsocket-native/src/main/c/ckmacros.h +++ b/junixsocket-native/src/main/c/ckmacros.h @@ -142,10 +142,23 @@ _Pragma("GCC diagnostic pop") #endif #define CK_UNUSED __attribute__((__unused__)) +#define CK_STRUCT_PACKED __attribute__((__packed__)) +#define CK_ALIGNED_8 __attribute__((aligned(8))) #if defined(_WIN32) || defined(__TOS_MVS__) # define CK_VISIBILITY_INTERNAL # define CK_VISIBILITY_DEFAULT +#elif __TANDEM +# define CK_VISIBILITY_INTERNAL +# define CK_VISIBILITY_DEFAULT +# undef CK_UNUSED +# define CK_UNUSED +# undef CK_STRUCT_PACKED +# define CK_STRUCT_PACKED +# undef CK_FALLTHROUGH +# define CK_FALLTHROUGH +# undef CK_ALIGNED_8 +# define CK_ALIGNED_8 #elif __clang # define CK_VISIBILITY_INTERNAL __attribute__((visibility("internal"))) # define CK_VISIBILITY_DEFAULT __attribute__((visibility("default"))) diff --git a/junixsocket-native/src/main/c/config.c b/junixsocket-native/src/main/c/config.c index 31f5e46b7..c28e7b2b2 100644 --- a/junixsocket-native/src/main/c/config.c +++ b/junixsocket-native/src/main/c/config.c @@ -27,11 +27,29 @@ CK_VISIBILITY_INTERNAL int clock_gettime(int ignored CK_UNUSED, struct timespec spec->tv_nsec = time % 10000000LL * 100; return 0; } -# endif +#endif + +#if defined(_WIN32) +// There's a problem with a missing symbol using mingw 12.0 from Homebrew +// Use mingw 11 for the time being: +// +// brew remove mingw-w64 +// wget https://raw.githubusercontent.com/Homebrew/homebrew-core/0247512f8a852f36f14b11809ac08a402de1f9e5/Formula/m/mingw-w64.rb +// brew install ./mingw-w64.rb +// +// Temporary fix for mingw 12: +//FILE * __cdecl __imp___iob_func() { +// return NULL; +//} + +#endif #if defined(_OS400) +// https://www.ibm.com/docs/en/i/7.5?topic=exceptions-error-conditions int jux_mangleErrno(int err) { switch(err) { + case 3417: + return ECLOSED; // just in case IBM i PASE errno.h defines a different value for ECLOSED case 3418: // CP3418 Possible APAR condition or hardware failure // https://www.ibm.com/docs/en/i/7.5?topic=ssw_ibm_i_75/apis/gpeern.htm return EINVAL; @@ -40,3 +58,29 @@ int jux_mangleErrno(int err) { } } #endif + +#if defined(_WIN32) +int jux_mangleErrno(int err) { + switch(err) { + case WSAEWOULDBLOCK: + return EWOULDBLOCK; + case WSAEINPROGRESS: + return EINPROGRESS; + case WSAEALREADY: + return EALREADY; + case 232: // Windows may throw this error code. "Connection reset by peer" + case WSAECONNRESET: + return ECONNRESET; + case WSAECONNABORTED: + return ECONNABORTED; + case WSAEISCONN: + return EISCONN; + case WSAENOTSOCK: + return ENOTSOCK; + case WSAETIMEDOUT: + return ETIMEDOUT; + default: + return err; + } +} +#endif diff --git a/junixsocket-native/src/main/c/config.h b/junixsocket-native/src/main/c/config.h index 777db6485..e93953f88 100644 --- a/junixsocket-native/src/main/c/config.h +++ b/junixsocket-native/src/main/c/config.h @@ -46,6 +46,12 @@ CK_IGNORE_RESERVED_IDENTIFIER_END # define _POSIX_SOURCE #endif +#if _TPF_SOURCE +// s390x-ibm-tpf-gcc may errneously generate these +# undef __GLIBC__ +# undef __linux__ +#endif + #include #include #if __TOS_MVS__ @@ -60,19 +66,35 @@ CK_IGNORE_RESERVED_IDENTIFIER_END #include #include +#if __TANDEM +#include +#endif + CK_IGNORE_UNUSED_MACROS_BEGIN #define junixsocket_have_sun_len // might be undef'ed below #define junixsocket_have_ancillary // might be undef'ed below #define junixsocket_have_pipe2 // might be undef'ed below CK_IGNORE_UNUSED_MACROS_END -#if !defined(false) +#if __TANDEM +# include +#elif !defined(false) # define false JNI_FALSE # define true JNI_TRUE # define bool jboolean #endif #if __TOS_MVS__ +// z/OS +# undef junixsocket_have_ancillary +# undef junixsocket_have_pipe2 +# define junixsocket_use_poll_for_read +# define junixsocket_use_poll_for_accept +#endif + +#if _TPF_SOURCE +// z/TPF +# undef junixsocket_have_sun_len # undef junixsocket_have_ancillary # undef junixsocket_have_pipe2 # define junixsocket_use_poll_for_read @@ -87,7 +109,13 @@ CK_IGNORE_UNUSED_MACROS_END # define JUNIXSOCKET_HARDEN_CMSG_NXTHDR 1 #endif -#if !defined(uint64_t) && !defined(_INT64_TYPE) && !defined(_UINT64_T) && !defined(_UINT64_T_DEFINED_) +#if defined(__HAIKU__) +# define junixsocket_use_poll_for_accept +# undef junixsocket_have_pipe2 +#endif + +#if __TANDEM +#elif !defined(uint64_t) && !defined(_INT64_TYPE) && !defined(_UINT64_T) && !defined(_UINT64_T_DEFINED_) # ifdef _LP64 typedef unsigned long uint64_t; # else @@ -172,6 +200,10 @@ int clock_gettime(int ignored CK_UNUSED, struct timespec *spec); #if __TOS_MVS__ // z/OS XLC doesn't have __has_include +#elif __TANDEM +# undef junixsocket_have_sun_len +# undef junixsocket_have_pipe2 +# undef junixsocket_have_ancillary #else # if __has_include() # include @@ -198,30 +230,51 @@ int clock_gettime(int ignored CK_UNUSED, struct timespec *spec); extern "C" { #endif -#if defined(SOCK_CLOEXEC) -#define junixsocket_have_accept4 -#define junixsocket_have_socket_cloexec +#if defined(__minix) +// Minix may have SOCKET_CLOEXEC, but no accept4 +# define junixsocket_have_socket_cloexec +#elif defined(SOCK_CLOEXEC) +# define junixsocket_have_accept4 +# define junixsocket_have_socket_cloexec #endif // Linux #ifdef __linux__ -#undef junixsocket_have_sun_len +# undef junixsocket_have_sun_len -#if !defined(JUNIXSOCKET_HARDEN_CMSG_NXTHDR) +# if !defined(JUNIXSOCKET_HARDEN_CMSG_NXTHDR) // workaround for systems using musl libc -# define JUNIXSOCKET_HARDEN_CMSG_NXTHDR 1 -#endif +# define JUNIXSOCKET_HARDEN_CMSG_NXTHDR 1 +# endif -#include -#include -#define junixsocket_have_tipc 1 +# include +# include +# define junixsocket_have_tipc 1 + +// This is not strictly necessary for Linux proper, +// but enabling this feature unbreaks Linux emulation on FreeBSD +// (disabled until we can figure out how to avoid the overhead unless we're in Linuxulator) +// # define junixsocket_use_poll_for_accept + +#endif // __linux__ + +#if __TOS_MVS__ +// z/OS XLC doesn't have __has_include +#elif __TANDEM +#else #if defined(AF_VSOCK) && __has_include() #include #define junixsocket_have_vsock 1 #endif + +#if defined(__MACH__) && __has_include() +# include +# define junixsocket_have_system 1 #endif +#endif // not __TOS_MVS_ + // Solaris #if defined(__sun) || defined(__sun__) #undef junixsocket_have_sun_len @@ -280,7 +333,7 @@ typedef unsigned long socklen_t; /* 64-bits */ #undef junixsocket_have_pipe2 #endif -#if !defined(_WIN32) +#if !defined(_WIN32) && !defined(__TANDEM) # include #endif #include @@ -292,36 +345,30 @@ typedef unsigned long socklen_t; /* 64-bits */ // Windows requires us fetching errno for socket-related errors #if defined(_WIN32) -# define socket_errno (errno = WSAGetLastError()) +int jux_mangleErrno(int); +# define socket_errno (errno = jux_mangleErrno(WSAGetLastError())) # define ssize_t int #elif defined(_OS400) CK_VISIBILITY_INTERNAL int jux_mangleErrno(int); # define socket_errno (errno = jux_mangleErrno(errno)) + +# if !defined(ECLOSED) +// IBM i PASE doesn't define "ECLOSED" but we may encounter it +# define ECLOSED 3417 +# endif #else # define socket_errno errno #endif #if __GLIBC__ +// This allows us to link against older glibc versions +# include "stat_wrapper.h" +# define stat(...) ck_stat(__VA_ARGS__) # if __CROSSCLANG_NODEPS__ -// nothing to do +// # else -// This allows us to link against older glibc versions # define memcpy memmove -# ifndef _STAT_VER -# if defined(__aarch64__) || defined(__riscv) -# define _STAT_VER 0 -# elif defined(__x86_64__) -# define _STAT_VER 1 -# else -# define _STAT_VER 3 -# endif -# endif -# if !defined(__xstat) -extern int __xstat (int __ver, const char *__filename, - struct stat *__stat_buf) __THROW __nonnull ((2, 3)); -# endif -# define stat(...) __xstat(_STAT_VER, __VA_ARGS__) # endif #endif diff --git a/junixsocket-native/src/main/c/connect.c b/junixsocket-native/src/main/c/connect.c index 91b964464..c4c413bc7 100644 --- a/junixsocket-native/src/main/c/connect.c +++ b/junixsocket-native/src/main/c/connect.c @@ -35,6 +35,14 @@ JNIEXPORT jboolean JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_connect JNIEnv * env, jclass clazz CK_UNUSED, jobject ab, jint abLen, jobject fd, jlong expectedInode) { + jboolean existingConnectOK; + if(expectedInode == -2) { + expectedInode = -1; + existingConnectOK = true; + } else { + existingConnectOK = false; + } + jux_sockaddr_t *addr = (*env)->GetDirectBufferAddress(env, ab); socklen_t addrLength = (socklen_t)abLen; if(addrLength == 0) { @@ -80,7 +88,9 @@ JNIEXPORT jboolean JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_connect } while(ret == -1 && myErr == EINTR); if(ret == -1) { - if(checkNonBlocking(socketHandle, myErr)) { + if(myErr == EISCONN && existingConnectOK) { + return true; + } else if(checkNonBlocking(socketHandle, myErr)) { // non-blocking connect return false; } else { diff --git a/junixsocket-native/src/main/c/credentials.c b/junixsocket-native/src/main/c/credentials.c index 7725f635d..6ad451f1b 100644 --- a/junixsocket-native/src/main/c/credentials.c +++ b/junixsocket-native/src/main/c/credentials.c @@ -55,8 +55,10 @@ static int ucredFromPid(pid_t pid, struct xucred* cr) cr->cr_uid = process.kp_eproc.e_ucred.cr_uid; cr->cr_ngroups = process.kp_eproc.e_ucred.cr_ngroups; + + gid_t *groups = process.kp_eproc.e_ucred.cr_groups; for(int i=cr->cr_ngroups-1;i>=0;i--) { - cr->cr_groups[i] =process.kp_eproc.e_ucred.cr_groups[i]; + cr->cr_groups[i] = groups[i]; } return ret; @@ -175,7 +177,7 @@ JNIEXPORT jobject JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_peerCreden ucred_free(uc); } # elif defined(LOCAL_PEERCRED) - struct xucred cr = {}; + struct xucred cr = {0}; { socklen_t len = sizeof(cr); diff --git a/junixsocket-native/src/main/c/exceptions.c b/junixsocket-native/src/main/c/exceptions.c index 3d1a28d9a..67851ac5d 100644 --- a/junixsocket-native/src/main/c/exceptions.c +++ b/junixsocket-native/src/main/c/exceptions.c @@ -34,6 +34,11 @@ static char *kExceptionClassnames[kExceptionMaxExcl] = { "org/newsclub/net/unix/InvalidArgumentSocketException", // kExceptionInvalidArgumentSocketException "org/newsclub/net/unix/AddressUnavailableSocketException", // kExceptionAddressUnavailableSocketException "org/newsclub/net/unix/OperationNotSupportedSocketException", // kExceptionOperationNotSupportedSocketException + "org/newsclub/net/unix/NoSuchDeviceSocketException", // kExceptionNoSuchDeviceSocketException + "org/newsclub/net/unix/BrokenPipeSocketException", // kExceptionBrokenPipeSocketException + "org/newsclub/net/unix/ConnectionResetSocketException", // kExceptionConnectionResetSocketException + "org/newsclub/net/unix/SocketClosedException", // kExceptionSocketClosedException + "org/newsclub/net/unix/NotConnectedSocketException", // kExceptionNotConnectedSocketException }; static jclass *kExceptionClasses; @@ -46,6 +51,12 @@ void init_exceptions(JNIEnv *env) { for (int i=0; iGetMethodID(env, exc, "", "(Ljava/lang/String;)V"); @@ -97,12 +108,16 @@ void _throwException(JNIEnv* env, ExceptionType exceptionType, char* message) void _throwErrnumException(JNIEnv* env, int errnum, jobject fdToClose) { ExceptionType exceptionType; + +#if ENOTSUP + if(errnum == ENOTSUP) { + errnum = EOPNOTSUPP; + } +#endif + switch(errnum) { case EAGAIN: case ETIMEDOUT: -#if defined(_WIN32) - case WSAETIMEDOUT: -#endif exceptionType = kExceptionSocketTimeoutException; break; case EHOSTUNREACH: @@ -115,12 +130,57 @@ void _throwErrnumException(JNIEnv* env, int errnum, jobject fdToClose) exceptionType = kExceptionAddressUnavailableSocketException; break; case EOPNOTSUPP: - exceptionType = kExceptionAddressUnavailableSocketException; +#if EPROTOTYPE + case EPROTOTYPE: +#endif +#if EPROTONOSUPPORT + case EPROTONOSUPPORT: +#endif +#if ESOCKTNOSUPPORT + case ESOCKTNOSUPPORT: +#endif +#if EPFNOSUPPORT + case EPFNOSUPPORT: +#endif +#if EAFNOSUPPORT + case EAFNOSUPPORT: +#endif + exceptionType = kExceptionOperationNotSupportedSocketException; + break; + case ENODEV: + exceptionType = kExceptionNoSuchDeviceSocketException; + break; + case ENOTCONN: + exceptionType = kExceptionNotConnectedSocketException; + if(fdToClose != NULL) { + _closeFd(env, fdToClose, -1); + } break; case EPIPE: - case EBADF: + exceptionType = kExceptionBrokenPipeSocketException; + if(fdToClose != NULL) { + _closeFd(env, fdToClose, -1); + } + break; case ECONNRESET: - // broken pipe, etc. -> close socket fd, so Socket#isClosed returns true + exceptionType = kExceptionConnectionResetSocketException; + if(fdToClose != NULL) { + _closeFd(env, fdToClose, -1); + } + break; +#if ECLOSED + case ECLOSED: +#endif + case ECONNABORTED: + // upon accept(2) either ECONNABORTED (seen on macOS) or ECLOSED (seen on IBM i OS/400) + // may indicate that the server socket has been closed + exceptionType = kExceptionSocketClosedException; + if(fdToClose != NULL) { + _closeFd(env, fdToClose, -1); + } + break; + case EBADF: + // close socket fd, so Socket#isClosed returns true if(fdToClose != NULL) { _closeFd(env, fdToClose, -1); } @@ -135,17 +195,13 @@ void _throwErrnumException(JNIEnv* env, int errnum, jobject fdToClose) #ifdef __linux__ __auto_type otherBuf = strerror_r(errnum, message, buflen); - if(CK_IGNORE_CAST_BEGIN (int)otherBuf CK_IGNORE_CAST_END > 255) { + if(CK_IGNORE_CAST_BEGIN (unsigned int)otherBuf CK_IGNORE_CAST_END > 255) { // strerror_r is ill-defined. strncpy(message, CK_IGNORE_CAST_BEGIN (char *)otherBuf CK_IGNORE_CAST_END, buflen); } #elif defined(_WIN32) - if(errnum == 232) { - // Windows may throw this error code. "Connection reset by peer" - errnum = ECONNRESET; - } if(errnum >= 10000) { // winsock error FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, @@ -188,7 +244,7 @@ void _throwSockoptErrnumException(JNIEnv* env, int errnum, jobject fd) // when setsockopt returns an error with EINVAL, it may mean the socket was shut down already if(errnum == EINVAL) { int handle = _getFD(env, fd); - struct sockaddr addr = {}; + struct sockaddr addr = {0}; socklen_t len = 0; int ret = getsockname(handle, &addr, &len); if(ret == -1) { diff --git a/junixsocket-native/src/main/c/exceptions.h b/junixsocket-native/src/main/c/exceptions.h index 48e61a24d..56a75d9fc 100644 --- a/junixsocket-native/src/main/c/exceptions.h +++ b/junixsocket-native/src/main/c/exceptions.h @@ -32,6 +32,11 @@ typedef enum { kExceptionInvalidArgumentSocketException, kExceptionAddressUnavailableSocketException, kExceptionOperationNotSupportedSocketException, + kExceptionNoSuchDeviceSocketException, + kExceptionBrokenPipeSocketException, + kExceptionConnectionResetSocketException, + kExceptionSocketClosedException, + kExceptionNotConnectedSocketException, kExceptionMaxExcl } ExceptionType; diff --git a/junixsocket-native/src/main/c/filedescriptors.c b/junixsocket-native/src/main/c/filedescriptors.c index 1a5ca92be..f2208a669 100644 --- a/junixsocket-native/src/main/c/filedescriptors.c +++ b/junixsocket-native/src/main/c/filedescriptors.c @@ -24,7 +24,10 @@ #include "address.h" static jclass class_FileDescriptor = NULL; + static jfieldID fieldID_fd = NULL; +static jmethodID methodID_getFd = NULL; +static jmethodID methodID_setFd = NULL; #if defined(_WIN32) static jfieldID fieldID_handle = NULL; @@ -44,6 +47,10 @@ typedef enum { #if junixsocket_have_vsock kFDTypeAFVSOCKStreamSocket, kFDTypeAFVSOCKDatagramSocket, +#endif +#if junixsocket_have_system + kFDTypeAFSYSTEMStreamSocket, + kFDTypeAFSYSTEMDatagramSocket, #endif kFDTypeMaxExcl } FileDescriptorType; @@ -56,6 +63,10 @@ static char* const kClassnameAFTIPCDatagramSocket = "org/newsclub/net/unix/tipc/ static char* const kClassnameAFVSOCKSocket = "org/newsclub/net/unix/vsock/AFVSOCKSocket"; static char* const kClassnameAFVSOCKDatagramSocket = "org/newsclub/net/unix/vsock/AFVSOCKDatagramSocket"; #endif +#if junixsocket_have_system +static char* const kClassnameAFSYSTEMSocket = "org/newsclub/net/unix/darwin/system/AFSYSTEMSocket"; +static char* const kClassnameAFSYSTEMDatagramSocket = "org/newsclub/net/unix/darwin/system/AFSYSTEMDatagramSocket"; +#endif // NOTE: The exceptions must all be either inherit from IOException or RuntimeException/Error static char *kFDTypeClassNames[kFDTypeMaxExcl] = { @@ -73,6 +84,10 @@ static char *kFDTypeClassNames[kFDTypeMaxExcl] = { kClassnameAFVSOCKSocket, kClassnameAFVSOCKDatagramSocket, #endif +#if junixsocket_have_system + kClassnameAFSYSTEMSocket, + kClassnameAFSYSTEMDatagramSocket, +#endif }; static jclass *kFDTypeClasses; @@ -102,11 +117,30 @@ void init_filedescriptors(JNIEnv *env) { // therefore it's OK if these classes are missing || (classname == kClassnameAFVSOCKSocket || classname == kClassnameAFVSOCKDatagramSocket) #endif +#if junixsocket_have_system + // Even if AF_SYSTEM is technically available, the junixsocket-system jar may not be in the classpath, + // therefore it's OK if these classes are missing + || (classname == kClassnameAFSYSTEMSocket || classname == kClassnameAFSYSTEMDatagramSocket) +#endif ); } class_FileDescriptor = kFDTypeClasses[0]; fieldID_fd = (*env)->GetFieldID(env, class_FileDescriptor, "fd", "I"); + if (fieldID_fd == NULL) { + (*env)->ExceptionClear(env); + + // https://github.com/AndroidSDKSources/android-sdk-sources-for-api-level-33/blob/master/java/io/FileDescriptor.java + methodID_getFd = (*env)->GetMethodID(env, class_FileDescriptor, "getInt$", "()I"); + (*env)->ExceptionClear(env); + methodID_setFd = (*env)->GetMethodID(env, class_FileDescriptor, "setInt$", "(I)V"); + + if(methodID_getFd == NULL || methodID_setFd == NULL) { + (*env)->ExceptionClear(env); + fieldID_fd = (*env)->GetFieldID(env, class_FileDescriptor, "descriptor", "I"); + } + } + #if defined(_WIN32) fieldID_handle = (*env)->GetFieldID(env, class_FileDescriptor, "handle", "J"); #endif @@ -147,11 +181,25 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_getFD jint _getFD(JNIEnv * env, jobject fd) { + if(fieldID_fd == NULL && methodID_getFd != NULL) { + // Android + return (*env)->CallIntMethod(env, fd, methodID_getFd); + } + return (*env)->GetIntField(env, fd, fieldID_fd); } void _initFD(JNIEnv * env, jobject fd, jint handle) { + if(fieldID_fd == NULL && methodID_setFd != NULL) { + // Android + (*env)->CallVoidMethod(env, fd, methodID_setFd, handle); + if((*env)->ExceptionCheck(env)) { + return; + } + return; + } + (*env)->SetIntField(env, fd, fieldID_fd, handle); } @@ -174,9 +222,9 @@ void _initHandle(JNIEnv * env, jobject fd, jlong handle) int _closeFd(JNIEnv * env, jobject fd, int handle) { int ret = 0; + if(fd == NULL) { if(handle >= 0) { - shutdown(handle, SHUT_RDWR); #if defined(_WIN32) ret = closesocket(handle); #else @@ -185,11 +233,29 @@ int _closeFd(JNIEnv * env, jobject fd, int handle) } return ret; } + + // Android doesn't like it when we call MonitorEnter on a pending exception. + // Temporarily hold on to that exception, and throw again afterwards. + jthrowable throwable = (*env)->ExceptionOccurred(env); + if(throwable != NULL) { + (*env)->ExceptionClear(env); + } + (*env)->MonitorEnter(env, fd); + int fdHandle = _getFD(env, fd); + _initFD(env, fd, -1); +#if defined(_WIN32) + jlong handleWin = _getHandle(env, fd); + _initHandle(env, fd, -1); +#endif + (*env)->MonitorExit(env, fd); + + if(throwable != NULL) { + (*env)->Throw(env, throwable); + } #if defined(_WIN32) jboolean isSocket; - jlong handleWin = _getHandle(env, fd); if(handleWin > 0) { if(handle >= 0) { _close(handle); @@ -202,39 +268,21 @@ int _closeFd(JNIEnv * env, jobject fd, int handle) } #else if(handle >= 0) { - shutdown(handle, SHUT_RDWR); ret = close(handle); } - -#endif - - int fdHandle = _getFD(env, fd); - _initFD(env, fd, -1); -#if defined(_WIN32) - _initHandle(env, fd, -1); -#endif - (*env)->MonitorExit(env, fd); - - if(handle >= 0) { - if(fdHandle >= 0 && handle != fdHandle) { -#if DEBUG - fprintf(stderr, "NativeUnixSocket_closeFd inconsistency: handle %i vs fdHandle %i\n", handle, fdHandle); - fflush(stderr); #endif - } - } if(fdHandle >= 0) { #if defined(_WIN32) if(isSocket) { - shutdown(fdHandle, SHUT_RDWR); ret = closesocket(fdHandle); } else { ret = _close(fdHandle); } #else - shutdown(fdHandle, SHUT_RDWR); - ret = close(fdHandle); + if(fdHandle != handle) { // if they're identical, we've handled it above already + ret = close(fdHandle); + } #endif } @@ -253,12 +301,8 @@ JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_close _throwException(env, kExceptionNullPointerException, "fd"); return; } - (*env)->MonitorEnter(env, fd); - int handle = _getFD(env, fd); - _initFD(env, fd, -1); - (*env)->MonitorExit(env, fd); - int ret = _closeFd(env, fd, handle); + int ret = _closeFd(env, fd, -1); if(ret == -1) { _throwErrnumException(env, errno, NULL); return; @@ -277,15 +321,47 @@ JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_shutdown int ret = shutdown(handle, mode); if(ret == -1) { int errnum = socket_errno; - if(errnum == ENOTCONN || errnum == EINVAL || errnum == EBADF) { - // ignore - return; + switch(errnum) { + case ENOTCONN: + case EINVAL: + case EBADF: + case EPIPE: + // ignore + return; + default: + _throwErrnumException(env, errnum, fd); + return; } - _throwErrnumException(env, errnum, fd); - return; } } +/* + * Class: org_newsclub_net_unix_NativeUnixSocket + * Method: checkBlocking + * Signature: (Ljava/io/FileDescriptor;)I + */ +JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_checkBlocking + (JNIEnv *env, jclass clazz CK_UNUSED, jobject fd) { + int handle = _getFD(env, fd); +#if defined(_WIN32) + CK_ARGUMENT_POTENTIALLY_UNUSED(handle); + // Windows doesn't provide current API to check for blocking state + return 2; // "indeterminate; needs re-configure" +#else + int flags = fcntl(handle, F_GETFL); + if(flags == -1) { + _throwErrnumException(env, socket_errno, NULL); + return -1; + } + + if((flags & O_NONBLOCK) != 0) { + return 0; // "non-blocking" + } else { + return 1; // "blocking" + } +#endif +} + /* * Class: org_newsclub_net_unix_NativeUnixSocket * Method: configureBlocking @@ -297,7 +373,7 @@ JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_configureBloc #if defined(_WIN32) u_long mode = blocking ? 0 : 1; if(ioctlsocket(handle, FIONBIO, &mode) != NO_ERROR) { - if(socket_errno == WSAENOTSOCK) { + if(socket_errno == ENOTSOCK) { CK_IGNORE_CAST_BEGIN HANDLE h = (HANDLE)_get_osfhandle(handle); CK_IGNORE_CAST_END @@ -338,7 +414,7 @@ jboolean checkNonBlocking0(int handle, int errnum, int options) { #if defined(_WIN32) CK_ARGUMENT_POTENTIALLY_UNUSED(handle); return ((options & org_newsclub_net_unix_NativeUnixSocket_OPT_NON_BLOCKING) != 0) - && (errnum == 0 || errnum == WSAEWOULDBLOCK || errnum == 232 /* named pipes may return this? */); + && (errnum == 0 || errnum == EWOULDBLOCK || errnum == 232 /* named pipes may return this? */); #else if (errnum == EWOULDBLOCK || errnum == EAGAIN || errnum == EINPROGRESS) { CK_ARGUMENT_POTENTIALLY_UNUSED(options); @@ -399,11 +475,30 @@ JNIEXPORT jclass JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_primaryType ret = getsockname(handle, addr, &len); if(ret != 0) { int errnum = socket_errno; - if(errnum == ENOTSOCK) { - return kFDTypeClasses[kFDTypeOther]; + switch(errnum) { + case ENOTSOCK: + return kFDTypeClasses[kFDTypeOther]; + case ENOTCONN: + break; + case EOPNOTSUPP: + ret = getpeername(handle, addr, &len); + if(ret != 0) { + switch(errnum) { + case ENOTSOCK: + return kFDTypeClasses[kFDTypeOther]; + case EOPNOTSUPP: + case ENOTCONN: + break; + default: + _throwErrnumException(env, errnum, fd); + return NULL; + } + } + break; + default: + _throwErrnumException(env, errnum, fd); + return NULL; } - _throwErrnumException(env, errnum, fd); - return NULL; } #endif @@ -412,6 +507,7 @@ JNIEXPORT jclass JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_primaryType (char*) #endif &type, &typeLen); + if(ret != 0) { _throwErrnumException(env, socket_errno, fd); return NULL; @@ -450,6 +546,17 @@ JNIEXPORT jclass JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_primaryType default: return kFDTypeClasses[kFDTypeOtherSocket]; } +#endif +#if junixsocket_have_system + case AF_SYSTEM: + switch(type) { + case SOCK_STREAM: + return kFDTypeClasses[kFDTypeAFSYSTEMStreamSocket]; + case SOCK_DGRAM: + return kFDTypeClasses[kFDTypeAFSYSTEMDatagramSocket]; + default: + return kFDTypeClasses[kFDTypeAFSYSTEMDatagramSocket]; + } #endif default: switch(type) { @@ -501,3 +608,59 @@ jboolean supportsCastAsRedirect(void) { return kRedirectImplConstructor != NULL; #endif } + +/* + * Class: org_newsclub_net_unix_NativeUnixSocket + * Method: duplicate + * Signature: (Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;)Ljava/io/FileDescriptor; + */ +JNIEXPORT jobject JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_duplicate + (JNIEnv *env, jclass clazz CK_UNUSED, jobject source, jobject target) { + + int sourceFD = _getFD(env, source); + if(sourceFD == -1) { + // invalid fd, or Windows handle (not yet supported) + return NULL; + } + + int targetFD = _getFD(env, target); + + if(targetFD == -1) { + targetFD = dup(sourceFD); + } else { + targetFD = dup2(sourceFD, targetFD); + } + +#if defined(_WIN32) + if(targetFD == -1 && errno == EBADF) { + WSAPROTOCOL_INFO protocolInfo; + if(WSADuplicateSocket(sourceFD, GetCurrentProcessId(), &protocolInfo) != 0) { + _throwErrnumException(env, socket_errno, NULL); + return NULL; + } + SOCKET targetSocket = WSASocket(protocolInfo.iAddressFamily, + protocolInfo.iSocketType, + protocolInfo.iProtocol,&protocolInfo, 0, 0); + if(targetSocket == INVALID_SOCKET) { + _throwErrnumException(env, socket_errno, NULL); + return NULL; + } + + targetFD = targetSocket; + } +#endif + + if(targetFD == -1) { + _throwErrnumException(env, errno, NULL); + return NULL; + } + + if (targetFD >= 0) { +# if defined(FD_CLOEXEC) + fcntl(targetFD, F_SETFD, FD_CLOEXEC); // best effort +# endif + } + + _initFD(env, target, targetFD); + return target; +} diff --git a/junixsocket-native/src/main/c/gettod_zos.h b/junixsocket-native/src/main/c/gettod_zos.h index 875b25028..bed7e69ee 100644 --- a/junixsocket-native/src/main/c/gettod_zos.h +++ b/junixsocket-native/src/main/c/gettod_zos.h @@ -10,6 +10,8 @@ #ifndef _LIBCPP_SUPPORT_IBM_GETTOD_ZOS_H #define _LIBCPP_SUPPORT_IBM_GETTOD_ZOS_H +#if __TOS_MVS__ + #include static int gettimeofdayMonotonic(struct timespec* Output) { @@ -36,7 +38,7 @@ static int gettimeofdayMonotonic(struct timespec* Output) { uint64_t us = (Value.Hi >> 4); uint64_t ns = ((Value.Hi & 0x0F) << 8) + (Value.Lo >> 56); ns = (ns * 1000) >> 12; - us = us - 2208988800000000; + us = us - 2208988800000000LL; Output->tv_sec = us / 1000000; Output->tv_nsec = ns; @@ -44,4 +46,6 @@ static int gettimeofdayMonotonic(struct timespec* Output) { return 0; } +#endif // __TOS_MVS__ + #endif // _LIBCPP_SUPPORT_IBM_GETTOD_ZOS_H diff --git a/junixsocket-native/src/main/c/init.c b/junixsocket-native/src/main/c/init.c index 9a48dedcf..5c286b463 100644 --- a/junixsocket-native/src/main/c/init.c +++ b/junixsocket-native/src/main/c/init.c @@ -131,6 +131,7 @@ jboolean supportsZeroLengthSend(void) { JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_init (JNIEnv *env, jclass clazz CK_UNUSED) { + (*env)->EnsureLocalCapacity(env, 20); #if defined(_WIN32) WSADATA wsaData; int ret = WSAStartup(MAKEWORD(2,2), &wsaData); @@ -140,8 +141,8 @@ JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_init } #endif - init_exceptions(env); - init_capabilities(env); + init_exceptions(env); // should be first + init_reflection(env); init_unix(); init_filedescriptors(env); @@ -157,6 +158,8 @@ JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_init #endif init_poll(env); init_socketoptions(env); + + init_capabilities(env); // should be last } /* @@ -181,3 +184,14 @@ JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_destroy destroy_poll(env); destroy_socketoptions(env); } + +/* + * Class: org_newsclub_net_unix_NativeUnixSocket + * Method: noop + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_noop +(JNIEnv *env CK_UNUSED, jclass clazz CK_UNUSED) { + // no-op + return; +} diff --git a/junixsocket-native/src/main/c/jni/jniport.h b/junixsocket-native/src/main/c/jni/jniport.h index 4cc9840d7..314a32dbf 100644 --- a/junixsocket-native/src/main/c/jni/jniport.h +++ b/junixsocket-native/src/main/c/jni/jniport.h @@ -35,6 +35,9 @@ typedef __int64 jlong; #if __TOS_MVS__ #define JNIEXPORT +#elif __TANDEM +#define JNIEXPORT +#define JNICALL #else #define JNIEXPORT __attribute__((visibility ("default"))) #endif diff --git a/junixsocket-native/src/main/c/jniutil.c b/junixsocket-native/src/main/c/jniutil.c index e52a08135..2049d1a38 100644 --- a/junixsocket-native/src/main/c/jniutil.c +++ b/junixsocket-native/src/main/c/jniutil.c @@ -23,24 +23,31 @@ void handleFieldNotFound(JNIEnv *env, jobject instance, char *fieldName) { + (*env)->ExceptionClear(env); jmethodID classMethodId = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, instance), "getClass", "()Ljava/lang/Class;"); jobject classObject = (*env)->CallObjectMethod(env, instance, classMethodId); + (*env)->ExceptionClear(env); jmethodID methodId = (*env)->GetMethodID(env, - (*env)->GetObjectClass(env, classObject), "getSimpleName", + (*env)->GetObjectClass(env, classObject), "getName", "()Ljava/lang/String;"); jstring className = (jstring)(*env)->CallObjectMethod(env, classObject, methodId); + if ((*env)->ExceptionCheck(env)) { + return; + } + const char* classNameStr = (*env)->GetStringUTFChars(env, className, NULL); if(classNameStr == NULL) { return; // OOME } #define handleFieldNotFound_error_message_template "Cannot find '%s' in class %s" + size_t buflen = strlen(handleFieldNotFound_error_message_template) + strlen(fieldName) + strlen(classNameStr); char *message = calloc(1, buflen); CK_IGNORE_USED_BUT_MARKED_UNUSED_BEGIN @@ -67,8 +74,11 @@ void callObjectSetter(JNIEnv *env, jobject instance, char *methodName, return; } - __attribute__((aligned(8))) jobject array[] = {value}; - (*env)->CallObjectMethodA(env, instance, methodId, (jvalue*)array); + CK_ALIGNED_8 jobject array[] = {value}; + (*env)->CallVoidMethodA(env, instance, methodId, (jvalue*)array); + if ((*env)->ExceptionCheck(env)) { + return; + } } void setObjectFieldValue(JNIEnv *env, jobject instance, char *fieldName, diff --git a/junixsocket-native/src/main/c/org_newsclub_net_unix_NativeUnixSocket.h b/junixsocket-native/src/main/c/org_newsclub_net_unix_NativeUnixSocket.h index b165e1d27..5cdc9eb3a 100644 --- a/junixsocket-native/src/main/c/org_newsclub_net_unix_NativeUnixSocket.h +++ b/junixsocket-native/src/main/c/org_newsclub_net_unix_NativeUnixSocket.h @@ -7,16 +7,22 @@ #ifdef __cplusplus extern "C" { #endif +#undef org_newsclub_net_unix_NativeUnixSocket_DOMAIN_GENERIC +#define org_newsclub_net_unix_NativeUnixSocket_DOMAIN_GENERIC -1L #undef org_newsclub_net_unix_NativeUnixSocket_DOMAIN_UNIX #define org_newsclub_net_unix_NativeUnixSocket_DOMAIN_UNIX 1L #undef org_newsclub_net_unix_NativeUnixSocket_DOMAIN_TIPC #define org_newsclub_net_unix_NativeUnixSocket_DOMAIN_TIPC 30L #undef org_newsclub_net_unix_NativeUnixSocket_DOMAIN_VSOCK #define org_newsclub_net_unix_NativeUnixSocket_DOMAIN_VSOCK 40L +#undef org_newsclub_net_unix_NativeUnixSocket_DOMAIN_SYSTEM +#define org_newsclub_net_unix_NativeUnixSocket_DOMAIN_SYSTEM 32L #undef org_newsclub_net_unix_NativeUnixSocket_SOCK_STREAM #define org_newsclub_net_unix_NativeUnixSocket_SOCK_STREAM 1L #undef org_newsclub_net_unix_NativeUnixSocket_SOCK_DGRAM #define org_newsclub_net_unix_NativeUnixSocket_SOCK_DGRAM 2L +#undef org_newsclub_net_unix_NativeUnixSocket_SOCK_RAW +#define org_newsclub_net_unix_NativeUnixSocket_SOCK_RAW 3L #undef org_newsclub_net_unix_NativeUnixSocket_SOCK_RDM #define org_newsclub_net_unix_NativeUnixSocket_SOCK_RDM 4L #undef org_newsclub_net_unix_NativeUnixSocket_SOCK_SEQPACKET @@ -41,6 +47,12 @@ extern "C" { #define org_newsclub_net_unix_NativeUnixSocket_SOCKETSTATUS_BOUND 1L #undef org_newsclub_net_unix_NativeUnixSocket_SOCKETSTATUS_CONNECTED #define org_newsclub_net_unix_NativeUnixSocket_SOCKETSTATUS_CONNECTED 2L +#undef org_newsclub_net_unix_NativeUnixSocket_SHUT_RD +#define org_newsclub_net_unix_NativeUnixSocket_SHUT_RD 0L +#undef org_newsclub_net_unix_NativeUnixSocket_SHUT_WR +#define org_newsclub_net_unix_NativeUnixSocket_SHUT_WR 1L +#undef org_newsclub_net_unix_NativeUnixSocket_SHUT_RD_WR +#define org_newsclub_net_unix_NativeUnixSocket_SHUT_RD_WR 2L /* * Class: org_newsclub_net_unix_NativeUnixSocket * Method: init @@ -57,6 +69,14 @@ JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_init JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_destroy (JNIEnv *, jclass); +/* + * Class: org_newsclub_net_unix_NativeUnixSocket + * Method: noop + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_noop + (JNIEnv *, jclass); + /* * Class: org_newsclub_net_unix_NativeUnixSocket * Method: capabilities @@ -129,6 +149,14 @@ JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_disconnect JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_socketStatus (JNIEnv *, jclass, jobject); +/* + * Class: org_newsclub_net_unix_NativeUnixSocket + * Method: duplicate + * Signature: (Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;)Ljava/io/FileDescriptor; + */ +JNIEXPORT jobject JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_duplicate + (JNIEnv *, jclass, jobject, jobject); + /* * Class: org_newsclub_net_unix_NativeUnixSocket * Method: primaryType @@ -361,6 +389,14 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_poll JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_configureBlocking (JNIEnv *, jclass, jobject, jboolean); +/* + * Class: org_newsclub_net_unix_NativeUnixSocket + * Method: checkBlocking + * Signature: (Ljava/io/FileDescriptor;)I + */ +JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_checkBlocking + (JNIEnv *, jclass, jobject); + /* * Class: org_newsclub_net_unix_NativeUnixSocket * Method: socketPair @@ -433,6 +469,14 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_sockTypeToNat JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_vsockGetLocalCID (JNIEnv *, jclass); +/* + * Class: org_newsclub_net_unix_NativeUnixSocket + * Method: systemResolveCtlId + * Signature: (Ljava/io/FileDescriptor;Ljava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_systemResolveCtlId + (JNIEnv *, jclass, jobject, jstring); + #ifdef __cplusplus } #endif diff --git a/junixsocket-native/src/main/c/org_newsclub_net_unix_android_SocketInputStream.h b/junixsocket-native/src/main/c/org_newsclub_net_unix_android_SocketInputStream.h new file mode 100644 index 000000000..f1ae12049 --- /dev/null +++ b/junixsocket-native/src/main/c/org_newsclub_net_unix_android_SocketInputStream.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_newsclub_net_unix_android_SocketInputStream */ + +#ifndef _Included_org_newsclub_net_unix_android_SocketInputStream +#define _Included_org_newsclub_net_unix_android_SocketInputStream +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_newsclub_net_unix_android_SocketInputStream + * Method: socketRead0 + * Signature: (Ljava/io/FileDescriptor;[BIII)I + */ +JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_android_SocketInputStream_socketRead0 + (JNIEnv *, jobject, jobject, jbyteArray, jint, jint, jint); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/junixsocket-native/src/main/c/org_newsclub_net_unix_android_SocketOutputStream.h b/junixsocket-native/src/main/c/org_newsclub_net_unix_android_SocketOutputStream.h new file mode 100644 index 000000000..0fc60c877 --- /dev/null +++ b/junixsocket-native/src/main/c/org_newsclub_net_unix_android_SocketOutputStream.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_newsclub_net_unix_android_SocketOutputStream */ + +#ifndef _Included_org_newsclub_net_unix_android_SocketOutputStream +#define _Included_org_newsclub_net_unix_android_SocketOutputStream +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_newsclub_net_unix_android_SocketOutputStream + * Method: socketWrite0 + * Signature: (Ljava/io/FileDescriptor;[BII)V + */ +JNIEXPORT void JNICALL Java_org_newsclub_net_unix_android_SocketOutputStream_socketWrite0 + (JNIEnv *, jobject, jobject, jbyteArray, jint, jint); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/junixsocket-native/src/main/c/polling.c b/junixsocket-native/src/main/c/polling.c index 371993a15..e5fdbf3dc 100644 --- a/junixsocket-native/src/main/c/polling.c +++ b/junixsocket-native/src/main/c/polling.c @@ -355,8 +355,15 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_poll jintArray ropsObj = (*env)->GetObjectField(env, pollFdObj, fieldID_rops); struct pollfd* pollFd = calloc(nfds, sizeof(struct pollfd)); + if(pollFd == NULL) { + return 0; + } jint *buf = calloc(nfds, sizeof(jint)); + if(buf == NULL) { + free(pollFd); + return 0; + } (*env)->GetIntArrayRegion(env, opsObj, 0, nfds, buf); for(int i=0; iCallVoidMethod(env, ancSupp, kSetTipcErrorInfo, errInfo->errorCode, errInfo->dataLength); + if((*env)->ExceptionCheck(env)) { + return -1; + } } } else if(cmsg->cmsg_level == SOL_TIPC && cmsg->cmsg_type == TIPC_DESTNAME && len == 12) { CK_IGNORE_CAST_ALIGN_BEGIN @@ -189,6 +192,9 @@ static ssize_t recvmsg_wrapper(JNIEnv * env, int handle, jbyte *buf, jint length jmethodID kSetTipcDestName = getMethodID_setTipcDestName(); if(kSetTipcDestName != NULL) { (*env)->CallVoidMethod(env, ancSupp, kSetTipcDestName, addr->type, addr->lower, addr->upper); + if((*env)->ExceptionCheck(env)) { + return -1; + } } #endif } else { @@ -270,8 +276,12 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_read( jint returnValue; if(count < 0) { // read(2) returns -1 on error. Java throws an Exception. - _throwErrnumException(env, errno, fd); - returnValue = -1; + if(errno == EWOULDBLOCK && checkNonBlocking(handle, socket_errno)) { + returnValue = -2; + } else { + _throwErrnumException(env, errno, fd); + returnValue = -1; + } } else if(count == 0) { // read(2)/recv return 0 on EOF. Java returns -1. returnValue = -1; @@ -360,18 +370,14 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_receive } if(checkNonBlocking0(handle, theError, opt)) { - theError = errno; - // no data on non-blocking socket, or terminated connection? - if(count == 0 && theError != 0) { - _throwException(env, kExceptionClosedChannelException, NULL); - } else if(theError == 0 || theError == EAGAIN || theError == ETIMEDOUT + if(theError == 0 || theError == EAGAIN || theError == EWOULDBLOCK || theError == ETIMEDOUT #if defined(_WIN32) || theError == WSAETIMEDOUT #endif - || theError == EINTR) { + || theError == EINTR) { // just return 0 } else { - _throwErrnumException(env, errno, fd); + _throwErrnumException(env, theError, fd); } return 0; } else if(theError == EWOULDBLOCK) { @@ -385,7 +391,6 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_receive _throwErrnumException(env, theError, fd); } } - count = 0; - return (jint)count; + return 0; } diff --git a/junixsocket-native/src/main/c/reflection.c b/junixsocket-native/src/main/c/reflection.c index dbc83d162..23383a2a0 100644 --- a/junixsocket-native/src/main/c/reflection.c +++ b/junixsocket-native/src/main/c/reflection.c @@ -27,12 +27,30 @@ static jboolean doSetServerSocket = true; static jclass kClassAbstractSelectableChannel; static jmethodID kMethodRemoveKey; +static jboolean cap_largePorts = false; + +static jboolean checkCapLargePorts(JNIEnv *env); + +static jboolean dontInitServerImpl = false; + void init_reflection(JNIEnv *env) { kClassAbstractSelectableChannel = findClassAndGlobalRef(env, "java/nio/channels/spi/AbstractSelectableChannel"); if(kClassAbstractSelectableChannel) { kMethodRemoveKey = (*env)->GetMethodID(env, kClassAbstractSelectableChannel, "removeKey", "(Ljava/nio/channels/SelectionKey;)V"); + if(kMethodRemoveKey == NULL) { + (*env)->ExceptionClear(env); + + // https://android.googlesource.com/platform/libcore/+/cff1616/luni/src/main/java/java/nio/channels/spi/AbstractSelectableChannel.java + kMethodRemoveKey = (*env)->GetMethodID(env, kClassAbstractSelectableChannel, "deregister", "(Ljava/nio/channels/SelectionKey;)V"); + if(kMethodRemoveKey == NULL) { + (*env)->ExceptionClear(env); + } + } } + + cap_largePorts = checkCapLargePorts(env); } + void destroy_reflection(JNIEnv *env) { releaseClassGlobalRef(env, kClassAbstractSelectableChannel); } @@ -45,10 +63,26 @@ void destroy_reflection(JNIEnv *env) { JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_initServerImpl( JNIEnv * env, jclass clazz CK_UNUSED, jobject serverSocket, jobject impl) { + if(dontInitServerImpl) { + return; + } + + callObjectSetter(env, serverSocket, "", "(Ljava/net/SocketImpl;)V", impl); + if(!(*env)->ExceptionCheck(env)) { + // all done + return; + } + (*env)->ExceptionClear(env); + setObjectFieldValue(env, serverSocket, "impl", "Ljava/net/SocketImpl;", impl); + if((*env)->ExceptionCheck(env)) { + (*env)->ExceptionClear(env); + // cannot access impl (probably Android) - if (doSetServerSocket) { + dontInitServerImpl = true; + return; + } else if(doSetServerSocket) { // no longer present in Java 16 doSetServerSocket = setObjectFieldValueIfPossible(env, impl, "serverSocket", "Ljava/net/ServerSocket;", serverSocket); @@ -63,21 +97,22 @@ JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_initServerImp JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_setPort( JNIEnv * env, jclass clazz CK_UNUSED, jobject addr, jint port) { - jclass fileDescriptorClass = (*env)->GetObjectClass(env, addr); + jclass clsInetSocketAddress = (*env)->GetObjectClass(env, addr); jobject fieldObject = addr; jfieldID portField; - jfieldID holderField = (*env)->GetFieldID(env, fileDescriptorClass, + jfieldID holderField = (*env)->GetFieldID(env, clsInetSocketAddress, "holder", "Ljava/net/InetSocketAddress$InetSocketAddressHolder;"); if(holderField != NULL) { fieldObject = (*env)->GetObjectField(env, addr, holderField); jclass holderClass = (*env)->GetObjectClass(env, fieldObject); portField = (*env)->GetFieldID(env, holderClass, "port", "I"); } else { - portField = (*env)->GetFieldID(env, fileDescriptorClass, "port", "I"); + portField = (*env)->GetFieldID(env, clsInetSocketAddress, "port", "I"); } if(portField == NULL) { + (*env)->ExceptionClear(env); _throwException(env, kExceptionSocketException, "Cannot find field \"port\" in java.net.InetSocketAddress. Unsupported JVM?"); return; @@ -129,7 +164,7 @@ JNIEXPORT jobject JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_currentRMI } jobject connHandler = (*env)->CallObjectMethod(env, tl, tlGet); - if(connHandler == NULL) { + if((*env)->ExceptionCheck(env) || connHandler == NULL) { return NULL; } jclass connHandlerClass = (*env)->GetObjectClass(env, connHandler); @@ -159,7 +194,43 @@ JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_deregisterSel } if(kMethodRemoveKey != NULL) { (*env)->CallVoidMethod(env, chann, kMethodRemoveKey, key); + if((*env)->ExceptionCheck(env)) { + return; + } } else { // FIXME } } + +jboolean supportsLargePorts(void) { + return cap_largePorts; +} + +static jboolean checkCapLargePorts(JNIEnv *env) { + jclass cls = (*env)->FindClass(env, "java/net/InetSocketAddress"); + if((*env)->ExceptionCheck(env)) { + (*env)->ExceptionClear(env); + return false; + } + + jmethodID constructor = (*env)->GetMethodID(env, cls, "", "(I)V"); + if((*env)->ExceptionCheck(env)) { + (*env)->ExceptionClear(env); + return false; + } + + jobject instance = (*env)->NewObject(env, cls, constructor, 0); + if(instance == NULL) { + (*env)->ExceptionClear(env); + return false; + } + + Java_org_newsclub_net_unix_NativeUnixSocket_setPort(env, cls, instance, 65536); + + if((*env)->ExceptionCheck(env)) { + (*env)->ExceptionClear(env); + return false; + } else { + return true; + } +} diff --git a/junixsocket-native/src/main/c/reflection.h b/junixsocket-native/src/main/c/reflection.h index 8010c6d21..8bca68a6c 100644 --- a/junixsocket-native/src/main/c/reflection.h +++ b/junixsocket-native/src/main/c/reflection.h @@ -24,4 +24,6 @@ void init_reflection(JNIEnv *env); void destroy_reflection(JNIEnv *env); +jboolean supportsLargePorts(void); + #endif /* reflection_h */ diff --git a/junixsocket-native/src/main/c/send.c b/junixsocket-native/src/main/c/send.c index 5e14fc8ee..ca68a7ee3 100644 --- a/junixsocket-native/src/main/c/send.c +++ b/junixsocket-native/src/main/c/send.c @@ -28,6 +28,8 @@ #if __TOS_MVS__ # include +#elif __TANDEM +# include #else # if __has_include() # include @@ -76,6 +78,7 @@ ssize_t send_wrapper(int handle, jbyte *buf, jint length, jux_sockaddr_t *sendTo count = write(handle, (char*)buf, length); } } + // on macOS/BSD, send seems to not block if the send buffer is full, so we have to handle ENOBUFS if(count >= 0) { break; @@ -96,22 +99,13 @@ ssize_t send_wrapper(int handle, jbyte *buf, jint length, jux_sockaddr_t *sendTo break; } count = 0; // don't throw -#if defined(sched_yield) - sched_yield(); + + // see https://github.com/virtualsquare/vde-2/issues/19 + // and https://stackoverflow.com/questions/16555101/sendto-dgrams-do-not-block-for-enobufs-on-osx +#if defined(_WIN32) + SwitchToThread(); #else - struct pollfd fds[] = { - { - .fd = handle, - .events = (POLLOUT), - .revents = 0 - } - }; - -# if defined(_WIN32) - WSAPoll(fds, 1, -1); -# else - poll(fds, 1, -1); -# endif + sched_yield(); #endif continue; } @@ -155,7 +149,7 @@ ssize_t sendmsg_wrapper(JNIEnv * env, int handle, jbyte *buf, jint length, jux_s if(ancFdsLen > 0) { ancBuf = (*env)->GetIntArrayElements(env, ancFds, NULL); memcpy(data, ancBuf, ancFdsLen * sizeof(jint)); - (*env)->ReleaseIntArrayElements(env, ancFds, ancBuf, 0); + (*env)->ReleaseIntArrayElements(env, ancFds, ancBuf, JNI_ABORT); // ancFds is unmodified } cmsg = junixsocket_CMSG_NXTHDR(&msg, cmsg); diff --git a/junixsocket-native/src/main/c/socket.c b/junixsocket-native/src/main/c/socket.c index 5f5ebdcca..0d7beafe3 100644 --- a/junixsocket-native/src/main/c/socket.c +++ b/junixsocket-native/src/main/c/socket.c @@ -83,10 +83,18 @@ int sockTypeToNative(JNIEnv *env, int type) { return SOCK_STREAM; case org_newsclub_net_unix_NativeUnixSocket_SOCK_DGRAM: return SOCK_DGRAM; + case org_newsclub_net_unix_NativeUnixSocket_SOCK_RAW: + return SOCK_RAW; +#if defined(SOCK_RDM) + case org_newsclub_net_unix_NativeUnixSocket_SOCK_RDM: + return SOCK_RDM; +#endif +#if defined(SOCK_SEQPACKET) case org_newsclub_net_unix_NativeUnixSocket_SOCK_SEQPACKET: return SOCK_SEQPACKET; +#endif default: - _throwException(env, kExceptionSocketException, "Illegal type"); + _throwException(env, kExceptionSocketException, "Illegal or unsupported type"); return -1; } } @@ -127,10 +135,12 @@ JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_createSocket return; } + int protocol = 0; + #if defined(junixsocket_have_socket_cloexec) - handle = socket(domain, type | SOCK_CLOEXEC, 0); + handle = socket(domain, type | SOCK_CLOEXEC, protocol); if(handle == -1 && errno == EPROTONOSUPPORT) { - handle = socket(domain, type, 0); + handle = socket(domain, type, protocol); if(handle > 0) { # if defined(FD_CLOEXEC) fcntl(handle, F_SETFD, FD_CLOEXEC); // best effort @@ -138,7 +148,7 @@ JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_createSocket } } #else - handle = socket(domain, type, 0); + handle = socket(domain, type, protocol); #endif if(handle < 0) { _throwErrnumException(env, socket_errno, fd); @@ -180,6 +190,7 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_socketStatus if(ret != 0) { int errnum = socket_errno; switch(errnum) { + case EOPNOTSUPP: case EINVAL: case ENOTCONN: case ENOTSOCK: // OSv socketpair @@ -196,6 +207,7 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_socketStatus if(ret != 0) { int errnum = socket_errno; switch(errnum) { + case EOPNOTSUPP: case EINVAL: case ENOTCONN: case ENOTSOCK: // OSv socketpair diff --git a/junixsocket-native/src/main/c/socketoptions.c b/junixsocket-native/src/main/c/socketoptions.c index 0d71b07c8..f81e63bed 100644 --- a/junixsocket-native/src/main/c/socketoptions.c +++ b/junixsocket-native/src/main/c/socketoptions.c @@ -105,6 +105,9 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_getSocketOpti // Unsupported on z/OS return -1; #endif +#if __TANDEM + return -1; +#else struct timeval optVal; socklen_t optLen = sizeof(optVal); int ret = getsockopt(handle, SOL_SOCKET, optID, &optVal, &optLen); @@ -113,6 +116,7 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_getSocketOpti return -1; } return (jint)(optVal.tv_sec * 1000 + optVal.tv_usec / 1000); +#endif } else #endif if(optID == SO_LINGER) { @@ -167,7 +171,8 @@ JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_setSocketOpti // Unsupported on z/OS return; #endif - +#if __TANDEM +#else // NOTE: SO_RCVTIMEO == SocketOptions.SO_TIMEOUT = 0x1006 struct timeval optVal; optVal.tv_sec = value / 1000; @@ -179,6 +184,7 @@ JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_setSocketOpti _throwSockoptErrnumException(env, socket_errno, fd); return; } +#endif return; } else #endif @@ -257,8 +263,8 @@ struct tipc_group_req { # endif static jobject groupReqToJava(JNIEnv *env, void* valPtr, socklen_t valLen) { - if(valLen < sizeof(struct tipc_group_req)) { - if(valLen == sizeof(jint)) { + if(valLen < (socklen_t)sizeof(struct tipc_group_req)) { + if(valLen == (socklen_t)sizeof(jint)) { // response is just the group type return (*env)->CallStaticObjectMethod(env, kAFTIPCGroupRequestClass, kAFTIPCGroupRequestFromNative, *((jint*)valPtr),0,0,0); } diff --git a/junixsocket-native/src/main/c/socketpair.c b/junixsocket-native/src/main/c/socketpair.c index 9ec7eabc1..8ff31a864 100644 --- a/junixsocket-native/src/main/c/socketpair.c +++ b/junixsocket-native/src/main/c/socketpair.c @@ -175,11 +175,13 @@ JNIEXPORT void JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_socketPair .svm_cid = VMADDR_CID_RESERVED # endif }; +CK_IGNORE_CAST_ALIGN_BEGIN if(simulateSocketPair(env, domain, type, fd1, fd2, (jux_sockaddr_t*)&addr, sizeof(struct sockaddr_vm)) != 0) { _throwErrnumException(env, myerr, NULL); return; } +CK_IGNORE_CAST_ALIGN_END return; } #endif diff --git a/junixsocket-native/src/main/c/stat_wrapper.c b/junixsocket-native/src/main/c/stat_wrapper.c new file mode 100644 index 000000000..cbc25b79a --- /dev/null +++ b/junixsocket-native/src/main/c/stat_wrapper.c @@ -0,0 +1,45 @@ +// +// stat_wrapper.c +// junixsocket-native +// +// Created by Christian Kohlschütter +// + +#if __TOS_MVS__ +// +#else +#if __has_include() +# include +#endif + +#if __GLIBC__ +# include +# include "ckmacros.h" +# include "stat_wrapper.h" + +# ifndef _STAT_VER +# if defined(__aarch64__) || defined(__riscv) || defined(__loongarch64) +# define _STAT_VER 0 +# elif defined(__x86_64__) +# define _STAT_VER 1 +# else +# define _STAT_VER 3 +# endif +# endif + +CK_IGNORE_RESERVED_IDENTIFIER_BEGIN +extern int __attribute__((weak)) __xstat (int __ver, const char *__filename, + struct stat *__stat_buf) __THROW __nonnull ((2, 3)); +extern int __attribute__((weak)) stat (const char *__filename, + struct stat *__stat_buf) __THROW __nonnull ((1, 2)); +CK_IGNORE_RESERVED_IDENTIFIER_END + +int CK_VISIBILITY_INTERNAL ck_stat(const char *filename, struct stat *stat_buf) { + if(__xstat) { + return __xstat(_STAT_VER, filename, stat_buf); + } else { + return stat(filename, stat_buf); + } +} +#endif +#endif diff --git a/junixsocket-native/src/main/c/stat_wrapper.h b/junixsocket-native/src/main/c/stat_wrapper.h new file mode 100644 index 000000000..8ae202ae8 --- /dev/null +++ b/junixsocket-native/src/main/c/stat_wrapper.h @@ -0,0 +1,19 @@ +// +// stat_wrapper.h +// junixsocket-native +// +// Created by Christian Kohlschütter +// + +#if __TOS_MVS__ +// +#else +#if __has_include() +# include +#endif + +#if __GLIBC__ +int ck_stat(const char *__filename, struct stat *__stat_buf) __THROW __nonnull ((1, 2)); +#endif + +#endif diff --git a/junixsocket-native/src/main/c/tipc.c b/junixsocket-native/src/main/c/tipc.c index d754a1865..2bcda6511 100644 --- a/junixsocket-native/src/main/c/tipc.c +++ b/junixsocket-native/src/main/c/tipc.c @@ -72,10 +72,15 @@ JNIEXPORT jbyteArray JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_tipcGet close(fd); jsize len = (jsize)strnlen(req.node_id, TIPC_NODEID_LEN); + if(len == 0) { + return NULL; + } + jbyteArray buf = (*env)->NewByteArray(env, len); (*env)->SetByteArrayRegion(env, buf, 0, len, (jbyte*)&(req.node_id)); - return buf;} + return buf; +} /* * Class: org_newsclub_net_unix_NativeUnixSocket @@ -84,17 +89,24 @@ JNIEXPORT jbyteArray JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_tipcGet */ JNIEXPORT jbyteArray JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_tipcGetLinkName (JNIEnv *env, jclass clazz CK_UNUSED, jint peer, jint bearerId) { - struct tipc_sioc_ln_req req = { - .peer = peer, - .bearer_id = bearerId - }; - int fd = newTipcRDMSocket(); if(fd <= 0) { return NULL; } - int ret = ioctl(fd, SIOCGETLINKNAME, &req); + + // At one point, TIPC_MAX_LINK_NAME got changed from 60 to 68 + // Require a minimum size so we don't crash + size_t reqSize = MAX(76, sizeof(struct tipc_sioc_ln_req)); + struct tipc_sioc_ln_req *req = calloc(1, reqSize); + if(req == NULL) { + return NULL; + } + req->peer = peer; + req->bearer_id = bearerId; + + int ret = ioctl(fd, SIOCGETLINKNAME, req); if(ret < 0) { + free(req); int errnum = socket_errno; close(fd); if(errnum == ENOTTY) { @@ -106,9 +118,15 @@ JNIEXPORT jbyteArray JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_tipcGet } close(fd); - jsize len = (jsize)strnlen(req.linkname, TIPC_MAX_LINK_NAME); + jsize len = (jsize)strnlen(req->linkname, TIPC_MAX_LINK_NAME); + if(len == 0) { + free(req); + return NULL; + } + jbyteArray buf = (*env)->NewByteArray(env, len); - (*env)->SetByteArrayRegion(env, buf, 0, len, (jbyte*)&(req.linkname)); + (*env)->SetByteArrayRegion(env, buf, 0, len, (jbyte*)&(req->linkname)); + free(req); return buf; } diff --git a/junixsocket-native/src/main/nar/aol.properties b/junixsocket-native/src/main/nar/aol.properties index e8dcabf24..6f82f8c61 100644 --- a/junixsocket-native/src/main/nar/aol.properties +++ b/junixsocket-native/src/main/nar/aol.properties @@ -116,6 +116,94 @@ aarch64.Linux.clang.cpp.options=${junixsocket.native.clang.cpp.options} -target aarch64.Linux.clang.cpp.includes=${junixsocket.native.default.cpp.includes} aarch64.Linux.clang.cpp.excludes= +aarch64.Android.clang.linker.name=clang +aarch64.Android.clang.linker.options=${junixsocket.native.default.linker.options} -target ${junixsocket.native.llvm.target} +aarch64.Android.clang.c.compiler=clang +aarch64.Android.clang.c.defines=_GNU_SOURCE +aarch64.Android.clang.c.options=${junixsocket.native.clang.c.options} -I${project.basedir}/src/main/c/jni -target ${junixsocket.native.llvm.target} +aarch64.Android.clang.c.includes=${junixsocket.native.default.c.includes} +aarch64.Android.clang.c.excludes= +aarch64.Android.clang.java.include= +aarch64.Android.clang.java.runtimeDirectory=IGNORED +aarch64.Android.clang.lib.prefix=lib +aarch64.Android.clang.shared.prefix=lib +aarch64.Android.clang.static.extension=a +aarch64.Android.clang.shared.extension=so +aarch64.Android.clang.plugin.extension=so +aarch64.Android.clang.jni.extension=so +aarch64.Android.clang.executable.extension= +aarch64.Android.clang.cpp.compiler=clang++ +aarch64.Android.clang.cpp.defines=_GNU_SOURCE +aarch64.Android.clang.cpp.options=${junixsocket.native.clang.cpp.options} -target ${junixsocket.native.llvm.target} +aarch64.Android.clang.cpp.includes=${junixsocket.native.default.cpp.includes} +aarch64.Android.clang.cpp.excludes= + +i686.Android.clang.linker.name=clang +i686.Android.clang.linker.options=${junixsocket.native.default.linker.options} -target ${junixsocket.native.llvm.target} +i686.Android.clang.c.compiler=clang +i686.Android.clang.c.defines=_GNU_SOURCE +i686.Android.clang.c.options=${junixsocket.native.clang.c.options} -I${project.basedir}/src/main/c/jni -target ${junixsocket.native.llvm.target} +i686.Android.clang.c.includes=${junixsocket.native.default.c.includes} +i686.Android.clang.c.excludes= +i686.Android.clang.java.include= +i686.Android.clang.java.runtimeDirectory=IGNORED +i686.Android.clang.lib.prefix=lib +i686.Android.clang.shared.prefix=lib +i686.Android.clang.static.extension=a +i686.Android.clang.shared.extension=so +i686.Android.clang.plugin.extension=so +i686.Android.clang.jni.extension=so +i686.Android.clang.executable.extension= +i686.Android.clang.cpp.compiler=clang++ +i686.Android.clang.cpp.defines=_GNU_SOURCE +i686.Android.clang.cpp.options=${junixsocket.native.clang.cpp.options} -target ${junixsocket.native.llvm.target} +i686.Android.clang.cpp.includes=${junixsocket.native.default.cpp.includes} +i686.Android.clang.cpp.excludes= + +x86_64.Android.clang.linker.name=clang +x86_64.Android.clang.linker.options=${junixsocket.native.default.linker.options} -target ${junixsocket.native.llvm.target} +x86_64.Android.clang.c.compiler=clang +x86_64.Android.clang.c.defines=_GNU_SOURCE +x86_64.Android.clang.c.options=${junixsocket.native.clang.c.options} -I${project.basedir}/src/main/c/jni -target ${junixsocket.native.llvm.target} +x86_64.Android.clang.c.includes=${junixsocket.native.default.c.includes} +x86_64.Android.clang.c.excludes= +x86_64.Android.clang.java.include= +x86_64.Android.clang.java.runtimeDirectory=IGNORED +x86_64.Android.clang.lib.prefix=lib +x86_64.Android.clang.shared.prefix=lib +x86_64.Android.clang.static.extension=a +x86_64.Android.clang.shared.extension=so +x86_64.Android.clang.plugin.extension=so +x86_64.Android.clang.jni.extension=so +x86_64.Android.clang.executable.extension= +x86_64.Android.clang.cpp.compiler=clang++ +x86_64.Android.clang.cpp.defines=_GNU_SOURCE +x86_64.Android.clang.cpp.options=${junixsocket.native.clang.cpp.options} -target ${junixsocket.native.llvm.target} +x86_64.Android.clang.cpp.includes=${junixsocket.native.default.cpp.includes} +x86_64.Android.clang.cpp.excludes= + +arm.Android.clang.linker.name=clang +arm.Android.clang.linker.options=${junixsocket.native.default.linker.options} -target ${junixsocket.native.llvm.target} +arm.Android.clang.c.compiler=clang +arm.Android.clang.c.defines=_GNU_SOURCE +arm.Android.clang.c.options=${junixsocket.native.clang.c.options} -I${project.basedir}/src/main/c/jni -target ${junixsocket.native.llvm.target} +arm.Android.clang.c.includes=${junixsocket.native.default.c.includes} +arm.Android.clang.c.excludes= +arm.Android.clang.java.include= +arm.Android.clang.java.runtimeDirectory=IGNORED +arm.Android.clang.lib.prefix=lib +arm.Android.clang.shared.prefix=lib +arm.Android.clang.static.extension=a +arm.Android.clang.shared.extension=so +arm.Android.clang.plugin.extension=so +arm.Android.clang.jni.extension=so +arm.Android.clang.executable.extension= +arm.Android.clang.cpp.compiler=clang++ +arm.Android.clang.cpp.defines=_GNU_SOURCE +arm.Android.clang.cpp.options=${junixsocket.native.clang.cpp.options} -target ${junixsocket.native.llvm.target} +arm.Android.clang.cpp.includes=${junixsocket.native.default.cpp.includes} +arm.Android.clang.cpp.excludes= + ppc64.AIX.clang.linker.name=clang ppc64.AIX.clang.linker.options=${junixsocket.native.default.linker.options} -target ${junixsocket.native.llvm.target} -maix64 -Xcrossclang-use-gcc=powerpc-ibm-aix7.2-gcc -static-libgcc -s ppc64.AIX.clang.c.compiler=clang @@ -204,6 +292,28 @@ s390x.Linux.clang.cpp.options=${junixsocket.native.clang.cpp.options} -target ${ s390x.Linux.clang.cpp.includes=${junixsocket.native.default.cpp.includes} s390x.Linux.clang.cpp.excludes= +loongarch64.Linux.clang.linker.name=clang +loongarch64.Linux.clang.linker.options=${junixsocket.native.default.linker.options} -Xcrossclang-with-and-without-lc -target ${junixsocket.native.llvm.target} +loongarch64.Linux.clang.c.compiler=clang +loongarch64.Linux.clang.c.defines=_GNU_SOURCE +loongarch64.Linux.clang.c.options=${junixsocket.native.clang.c.options} -Xcrossclang-with-and-without-lc -I${project.basedir}/src/main/c/jni -target ${junixsocket.native.llvm.target} +loongarch64.Linux.clang.c.includes=${junixsocket.native.default.c.includes} +loongarch64.Linux.clang.c.excludes= +loongarch64.Linux.clang.java.include= +loongarch64.Linux.clang.java.runtimeDirectory=IGNORED +loongarch64.Linux.clang.lib.prefix=lib +loongarch64.Linux.clang.shared.prefix=lib +loongarch64.Linux.clang.static.extension=a +loongarch64.Linux.clang.shared.extension=so +loongarch64.Linux.clang.plugin.extension=so +loongarch64.Linux.clang.jni.extension=so +loongarch64.Linux.clang.executable.extension= +loongarch64.Linux.clang.cpp.compiler=clang++ +loongarch64.Linux.clang.cpp.defines=_GNU_SOURCE +loongarch64.Linux.clang.cpp.options=${junixsocket.native.clang.cpp.options} -target ${junixsocket.native.llvm.target} +loongarch64.Linux.clang.cpp.includes=${junixsocket.native.default.cpp.includes} +loongarch64.Linux.clang.cpp.excludes= + riscv64.Linux.clang.linker.name=clang riscv64.Linux.clang.linker.options=${junixsocket.native.default.linker.options} -Xcrossclang-with-and-without-lc -target ${junixsocket.native.llvm.target} -Xcrossclang-llvm-name llvm riscv64.Linux.clang.c.compiler=clang @@ -227,11 +337,11 @@ riscv64.Linux.clang.cpp.includes=${junixsocket.native.default.cpp.includes} riscv64.Linux.clang.cpp.excludes= amd64.Windows10.clang.linker.name=clang -amd64.Windows10.clang.linker.options=${junixsocket.native.default.linker.options} -target unspecified -Xcrossclang-use-gcc=x86_64-w64-mingw32-gcc -lws2_32 -ladvapi32 -lmswsock -Xcrossclang-output-strip-lib-prefix +amd64.Windows10.clang.linker.options=${junixsocket.native.default.linker.options} -target x86_64-w64-mingw32 -Xcrossclang-use-gcc=x86_64-w64-mingw32-gcc -lws2_32 -ladvapi32 -lmswsock -Xcrossclang-output-strip-lib-prefix #amd64.Windows10.clang.linker.options=${junixsocket.native.default.linker.options} -target ${junixsocket.native.llvm.target} -Xcrossclang-use-ldshim -Wl,--Xldshim-ld=x86_64-w64-mingw32-ld amd64.Windows10.clang.c.compiler=clang amd64.Windows10.clang.c.defines= -amd64.Windows10.clang.c.options=${junixsocket.native.clang.c.options} -I${project.basedir}/src/main/c/jni -target unspecified -Xcrossclang-use-gcc=x86_64-w64-mingw32-gcc -Xcrossclang-hide-unknown-warning-warnings +amd64.Windows10.clang.c.options=${junixsocket.native.clang.c.options} -I${project.basedir}/src/main/c/jni -target x86_64-w64-mingw32 -Xcrossclang-use-gcc=x86_64-w64-mingw32-gcc -Xcrossclang-hide-unknown-warning-warnings amd64.Windows10.clang.c.includes=${junixsocket.native.default.c.includes} amd64.Windows10.clang.c.excludes= amd64.Windows10.clang.java.include= @@ -427,7 +537,7 @@ amd64.NetBSD.clang.cpp.defines=_GNU_SOURCE amd64.NetBSD.clang.cpp.options=${junixsocket.native.clang.cpp.options} -target ${junixsocket.native.llvm.target} amd64.NetBSD.clang.cpp.includes=${junixsocket.native.default.cpp.includes} amd64.NetBSD.clang.cpp.excludes= -` + amd64.OpenBSD.linker=clang amd64.OpenBSD.clang.c.compiler=clang amd64.OpenBSD.clang.linker.name=clang diff --git a/junixsocket-native/src/site/site.xml b/junixsocket-native/src/site/site.xml index b781b5083..482312bc3 100644 --- a/junixsocket-native/src/site/site.xml +++ b/junixsocket-native/src/site/site.xml @@ -7,6 +7,7 @@ org.apache.maven.skins maven-fluido-skin + 2.0.0-M8 diff --git a/junixsocket-rmi/pom.xml b/junixsocket-rmi/pom.xml index 3e2d19a0d..19084fe3c 100644 --- a/junixsocket-rmi/pom.xml +++ b/junixsocket-rmi/pom.xml @@ -6,7 +6,7 @@ com.kohlschutter.junixsocket junixsocket - 2.6.1 + 2.10.1 ../pom.xml junixsocket-rmi @@ -22,26 +22,37 @@ com.kohlschutter.junixsocket junixsocket-common - ${project.version} com.kohlschutter.junixsocket junixsocket-server - ${project.version} com.kohlschutter.junixsocket junixsocket-native-common - ${project.version} test - - com.kohlschutter.junixsocket - junixsocket-native-custom - ${project.version} - test - default - + + + + with-native-custom + + + !junixsocket.native-custom.skip + + + + + com.kohlschutter.junixsocket + junixsocket-native-custom + ${project.version} + test + default + true + + + + diff --git a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFNaming.java b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFNaming.java index 9bb43c164..51822d3cc 100644 --- a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFNaming.java +++ b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFNaming.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,7 +48,7 @@ /** * The {@link AFSocket}-compatible equivalent of {@link Naming}. Use this class for accessing RMI * registries that are reachable by {@link AFSocket}s. - * + * * @author Christian Kohlschütter */ public abstract class AFNaming extends AFRegistryAccess { @@ -61,18 +61,17 @@ public abstract class AFNaming extends AFRegistryAccess { private final int registryPort; private final int servicePort; AFRMISocketFactory socketFactory; - private boolean remoteShutdownAllowed = true; + private final AtomicBoolean remoteShutdownAllowed = new AtomicBoolean(true); private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false); private final AtomicBoolean addedShutdownHook = new AtomicBoolean(false); /** * Creates a new naming instance with the given ports. - * + * * @param registryPort The registry port. * @param servicePort The port for AFRMIService. - * @throws IOException on error. */ - protected AFNaming(final int registryPort, final int servicePort) throws IOException { + protected AFNaming(final int registryPort, final int servicePort) { super(); this.registryPort = registryPort; this.servicePort = servicePort; @@ -80,7 +79,7 @@ protected AFNaming(final int registryPort, final int servicePort) throws IOExcep /** * Creates a new {@link AFRegistry} given a {@link Registry} implementation. - * + * * @param impl The implementation. * @return The new {@link AFRegistry} instance. * @throws RemoteException on error. @@ -89,7 +88,7 @@ protected AFNaming(final int registryPort, final int servicePort) throws IOExcep /** * Creates or returns the {@link AFRMISocketFactory} to be used with this instance. - * + * * @return The socket factory. * @throws IOException on error. */ @@ -107,7 +106,9 @@ static T getInstance(final int registryPort, try { instance = provider.newInstance(registryPort); Objects.requireNonNull(instance); - instance.socketFactory = instance.initSocketFactory(); + synchronized (instance) { + instance.socketFactory = instance.initSocketFactory(); + } } catch (RemoteException e) { throw e; } catch (IOException e) { @@ -121,7 +122,7 @@ static T getInstance(final int registryPort, /** * Returns the {@link AFRMISocketFactory} associated with this instance. - * + * * @return The {@link AFRMISocketFactory}. */ @SuppressFBWarnings("EI_EXPOSE_REP") @@ -131,7 +132,7 @@ public synchronized AFRMISocketFactory getSocketFactory() { /** * Returns the registry port. - * + * * @return The port. */ public final int getRegistryPort() { @@ -142,7 +143,8 @@ AFRMIService getRMIService() throws RemoteException, NotBoundException { return getRMIService(getRegistry()); } - AFRMIService getRMIService(AFRegistry reg) throws RemoteException, NotBoundException { + synchronized AFRMIService getRMIService(AFRegistry reg) throws RemoteException, + NotBoundException { if (rmiService == null) { this.rmiService = getRMIServiceFromRegistry(reg); } @@ -152,7 +154,7 @@ AFRMIService getRMIService(AFRegistry reg) throws RemoteException, NotBoundExcep AFRMIService getRMIServiceFromRegistry(AFRegistry reg) throws RemoteException, NotBoundException { AFRMIService service; service = (AFRMIService) reg.lookup(RMI_SERVICE_NAME, 5, TimeUnit.SECONDS); - this.remoteShutdownAllowed = service.isShutdownAllowed(); + this.remoteShutdownAllowed.set(service.isShutdownAllowed()); return service; } @@ -161,18 +163,17 @@ private void closeUponRuntimeShutdown() { ShutdownHookSupport.addWeakShutdownHook(new ShutdownHook() { @Override - public void onRuntimeShutdown(Thread thread) throws IOException { - synchronized (AFNaming.class) { - if (registry != null && registry.isLocal()) { - shutdownRegistry(); - } + @SuppressWarnings("LockOnNonEnclosingClassLiteral" /* errorprone */) + public synchronized void onRuntimeShutdown(Thread thread) throws IOException { + if (registry != null && registry.isLocal()) { + shutdownRegistry(); } } }); } } - private void rebindRMIService(final AFRMIService assigner) throws RemoteException { + private synchronized void rebindRMIService(final AFRMIService assigner) throws RemoteException { rmiService = assigner; getRegistry().rebind(RMI_SERVICE_NAME, assigner); } @@ -184,10 +185,10 @@ public AFRegistry getRegistry() throws RemoteException { /** * Returns a reference to the existing RMI registry. - * + * * If there's no registry running at this port after waiting for up to the given time, an * exception is thrown. - * + * * @param timeout The timeout value. * @param unit The timeout unit. * @return The registry. @@ -197,7 +198,7 @@ public AFRegistry getRegistry(long timeout, TimeUnit unit) throws RemoteExceptio if (shutdownInProgress.get()) { throw new ShutdownException(); } - synchronized (AFNaming.class) { + synchronized (this) { AFRegistry reg = getRegistry(false); if (reg == null) { reg = openRegistry(timeout, unit); @@ -208,7 +209,7 @@ public AFRegistry getRegistry(long timeout, TimeUnit unit) throws RemoteExceptio /** * Tries to access the registry, waiting some time if necessary. - * + * * @param timeout The timeout. * @param unit The unit for the timeout. * @return The registry instance. @@ -221,7 +222,7 @@ public AFRegistry getRegistry(long timeout, TimeUnit unit) throws RemoteExceptio * * If there's no registry running at this port, and {@code create} is set to {@code true}, a new * one is created; when {@code create} is set to {@code false}, {@code null} is returned. - * + * * @param create {@code true} if a new register may be created if necessary. * @return The registry, or {@code null} * @throws RemoteException If there was a problem. @@ -230,7 +231,7 @@ public AFRegistry getRegistry(boolean create) throws RemoteException { if (shutdownInProgress.get()) { throw new ShutdownException(); } - synchronized (AFNaming.class) { + synchronized (this) { if (registry != null) { return registry; } else if (!socketFactory.hasRegisteredPort(registryPort)) { @@ -262,11 +263,11 @@ private AFRegistry locateRegistry() throws RemoteException { /** * Shuts this RMI Registry down. - * + * * @throws RemoteException if the operation fails. */ public void shutdownRegistry() throws RemoteException { - synchronized (AFNaming.class) { + synchronized (this) { if (registry == null) { return; } @@ -309,8 +310,8 @@ public void shutdownRegistry() throws RemoteException { */ protected abstract void shutdownRegistryFinishingTouches(); - private void unexportRMIService(AFRegistry reg, AFRMIServiceImpl serv) throws AccessException, - RemoteException { + private synchronized void unexportRMIService(AFRegistry reg, AFRMIServiceImpl serv) + throws AccessException, RemoteException { if (serv != null) { serv.shutdownRegisteredCloseables(); } @@ -359,81 +360,79 @@ private void shutdownViaRMIService(AFRegistry reg, AFRMIService serv) throws Rem /** * Creates a new RMI {@link Registry}. - * + * * If there already was a registry created previously, it is shut down and replaced by the current * one. - * + * * Use {@link #getRegistry()} to try to reuse an existing registry. - * + * * @return The registry * @throws RemoteException if the operation fails. * @see #getRegistry() */ - public AFRegistry createRegistry() throws RemoteException { - synchronized (AFNaming.class) { - AFRegistry existingRegistry = registry; - if (existingRegistry == null) { - try { - existingRegistry = getRegistry(false); - } catch (ServerException e) { - Throwable cause = e.getCause(); - if (cause instanceof NotBoundException || cause instanceof ConnectIOException) { - existingRegistry = null; - } else { - throw e; - } + public synchronized AFRegistry createRegistry() throws RemoteException { + AFRegistry existingRegistry = registry; + if (existingRegistry == null) { + try { + existingRegistry = getRegistry(false); + } catch (ServerException e) { + Throwable cause = e.getCause(); + if (cause instanceof NotBoundException || cause instanceof ConnectIOException) { + existingRegistry = null; + } else { + throw e; } } - if (existingRegistry != null) { - if (!isRemoteShutdownAllowed()) { - throw new ServerException("The server refuses to be shutdown remotely"); - } - shutdownRegistry(); + } + if (existingRegistry != null) { + if (!isRemoteShutdownAllowed()) { + throw new ServerException("The server refuses to be shutdown remotely"); } + shutdownRegistry(); + } - initRegistryPrerequisites(); - - setRegistry(newAFRegistry(LocateRegistry.createRegistry(registryPort, socketFactory, - socketFactory))); + initRegistryPrerequisites(); + AFRegistry newAFRegistry = newAFRegistry(LocateRegistry.createRegistry(registryPort, + socketFactory, socketFactory)); + setRegistry(newAFRegistry); - final AFRMIService service = new AFRMIServiceImpl(this); - UnicastRemoteObject.exportObject(service, servicePort, socketFactory, socketFactory); + final AFRMIService service = new AFRMIServiceImpl(this); + UnicastRemoteObject.exportObject(service, servicePort, socketFactory, socketFactory); - rebindRMIService(service); + rebindRMIService(service); - return registry; - } + return registry; } /** * Called by {@link #createRegistry()} right before creating/setting the registry. - * + * * @throws ServerException on error. */ protected abstract void initRegistryPrerequisites() throws ServerException; /** * Checks if this {@link AFNaming} instance can be shut down remotely. - * + * * @return {@code true} if remote shutdown is allowed. */ public boolean isRemoteShutdownAllowed() { - return remoteShutdownAllowed; + return remoteShutdownAllowed.get(); } /** * Controls whether this {@link AFNaming} instance can be shut down remotely. - * + * * @param remoteShutdownAllowed {@code true} if remote shutdown is allowed. */ public void setRemoteShutdownAllowed(boolean remoteShutdownAllowed) { - this.remoteShutdownAllowed = remoteShutdownAllowed; + this.remoteShutdownAllowed.set(remoteShutdownAllowed); } /** * Exports and binds the given Remote object to the given name, using the given {@link AFNaming} * setup. - * + * * @param name The name to use to bind the object in the registry. * @param obj The object to export and bind. * @throws RemoteException if the operation fails. @@ -448,7 +447,7 @@ public void exportAndBind(String name, Remote obj) throws RemoteException, Alrea /** * Exports and re-binds the given Remote object to the given name, using the given * {@link AFNaming} setup. - * + * * @param name The name to use to bind the object in the registry. * @param obj The object to export and bind. * @throws RemoteException if the operation fails. @@ -478,9 +477,9 @@ public void unexportAndUnbind(String name, Remote obj) throws RemoteException { /** * Exports the given Remote object, using the given socket factory and a randomly assigned port. - * + * * NOTE: This helper function can also be used for regular RMI servers. - * + * * @param obj The object to export. * @param socketFactory The socket factory to use. * @return The remote stub. @@ -494,9 +493,9 @@ public static Remote exportObject(Remote obj, RMISocketFactory socketFactory) /** * Forcibly un-exports the given object, if it exists (otherwise returns without an error). This * should be called upon closing a {@link Closeable} {@link Remote} object. - * + * * NOTE: This helper function can also be used for regular RMI servers. - * + * * @param obj The object to un-export. */ public static void unexportObject(Remote obj) { @@ -507,14 +506,12 @@ public static void unexportObject(Remote obj) { } } - private void setRegistry(AFRegistry registry) { - synchronized (AFNaming.class) { - this.registry = registry; - if (registry == null) { - rmiService = null; - } else if (registry.isLocal()) { - closeUponRuntimeShutdown(); - } + private synchronized void setRegistry(AFRegistry registry) { + this.registry = registry; + if (registry == null) { + rmiService = null; + } else if (registry.isLocal()) { + closeUponRuntimeShutdown(); } } } diff --git a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFNamingProvider.java b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFNamingProvider.java index e412a1396..663280276 100644 --- a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFNamingProvider.java +++ b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFNamingProvider.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,17 +21,17 @@ /** * The key to accessing to {@link AFNaming} instances. - * + * * Implementors must guarantee that {@link #hashCode()} and {@link #equals(Object)} correctly * identify duplicate providers. - * + * * @param The actual {@link AFNaming} subclass. * @author Christian Kohlschütter */ public interface AFNamingProvider { /** * Creates a new {@link AFNaming} instance using the given registry port. - * + * * @param registryPort The registry port. * @return The {@link AFNaming} instance. * @throws IOException on error. diff --git a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFNamingRef.java b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFNamingRef.java index 92f010b97..fa5098ab9 100644 --- a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFNamingRef.java +++ b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFNamingRef.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ /** * A reference to a {@link AFNaming} instance. - * + * * @author Christian Kohlschütter */ final class AFNamingRef { diff --git a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRMIService.java b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRMIService.java index 10d7edd85..4a16625c2 100644 --- a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRMIService.java +++ b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRMIService.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,16 +25,16 @@ /** * The {@link AFRMIService} assigns and keeps track of anonymous ports, among other things. - * + * * This feature is to be used by {@link AFRMISocketFactory} only. - * + * * @author Christian Kohlschütter */ public interface AFRMIService extends Remote { /** * Registers a new anonymous port and returns it. When the port is not required anymore, it must * be returned via {@link #returnPort(int)}. - * + * * @return The new port. * @throws IOException if the operation fails. */ @@ -43,7 +43,7 @@ public interface AFRMIService extends Remote { /** * Returns a previously registered port. No error is thrown if the given port has not been * assigned before. - * + * * @param port The port. * @throws IOException if the operation fails. */ @@ -51,7 +51,7 @@ public interface AFRMIService extends Remote { /** * Returns a stream of open ports. - * + * * @return A sequence of open ports. * @throws RemoteException if the operation fails. */ @@ -59,9 +59,9 @@ public interface AFRMIService extends Remote { /** * Indicates whether a remote-shutdown of the RMI registry is allowed. - * + * * NOTE: A call to {@link #shutdown()} may or may not succeed regardless. - * + * * @return Indication of whether a remote-shutdown of the RMI registry is allowed. * @throws RemoteException if the operation fails. */ @@ -69,7 +69,7 @@ public interface AFRMIService extends Remote { /** * Asks that this RMI registry gets shut down. - * + * * @throws RemoteException if the operation fails. */ void shutdown() throws RemoteException; @@ -77,7 +77,7 @@ public interface AFRMIService extends Remote { /** * Adds the given {@link Closeable} to the list of instances to be closed upon shutdown of the RMI * registry. - * + * * @param closeable The instance. * @throws RemoteException if the operation fails. */ @@ -86,9 +86,9 @@ public interface AFRMIService extends Remote { /** * Removes the given {@link Closeable} from the list of instances to be closed upon shutdown of * the RMI registry. - * + * * No error is returned if the given element was not registered before. - * + * * @param closeable The instance. * @throws RemoteException if the operation fails. */ diff --git a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRMIServiceImpl.java b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRMIServiceImpl.java index 01d5083bb..caf52c4de 100644 --- a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRMIServiceImpl.java +++ b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRMIServiceImpl.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,11 +34,13 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.stream.IntStream; +import org.newsclub.net.unix.StackTraceUtil; + import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; /** * A very simple implementation of a {@link AFRMIService}. - * + * * @author Christian Kohlschütter */ final class AFRMIServiceImpl implements AFRMIService { @@ -120,7 +122,7 @@ public boolean isShutdownAllowed() throws RemoteException { public void registerForShutdown(Closeable closeable) throws RemoteException { synchronized (closeAtShutdown) { unregisterForShutdown(closeable); - closeAtShutdown.add(new WeakReference(closeable)); + closeAtShutdown.add(new WeakReference<>(closeable)); } } @@ -146,21 +148,18 @@ void shutdownRegisteredCloseables() { ExecutorService executor = Executors.newCachedThreadPool(); for (WeakReference ref : list) { - executor.submit(new Runnable() { - @Override - public void run() { - @SuppressWarnings("resource") - Closeable cl = ref.get(); - if (cl == null) { - return; - } - try { - cl.close(); - } catch (NoSuchObjectException e) { - // ignore - } catch (IOException e) { - e.printStackTrace(); - } + executor.execute(() -> { + @SuppressWarnings("resource") + Closeable cl = ref.get(); + if (cl == null) { + return; + } + try { + cl.close(); + } catch (NoSuchObjectException e) { + // ignore + } catch (IOException e) { + StackTraceUtil.printStackTrace(e); } }); } diff --git a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRMISocketFactory.java b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRMISocketFactory.java index 820fdfa5d..91b605758 100644 --- a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRMISocketFactory.java +++ b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRMISocketFactory.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,55 +36,61 @@ import org.newsclub.net.unix.AFServerSocket; import org.newsclub.net.unix.AFSocket; import org.newsclub.net.unix.AFSocketAddress; +import org.newsclub.net.unix.StackTraceUtil; import org.newsclub.net.unix.rmi.ShutdownHookSupport.ShutdownHook; import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; /** * An {@link RMISocketFactory} that supports {@link AFSocket}s. - * + * * @author Christian Kohlschütter */ public abstract class AFRMISocketFactory extends RMISocketFactory implements Externalizable, Closeable { private static final long serialVersionUID = 1L; - private RMIClientSocketFactory defaultClientFactory; - private RMIServerSocketFactory defaultServerFactory; + private transient AFRMIService rmiService = null; - private AFNaming naming; + private transient Externables externables; + private final transient Map> openServerSockets = new HashMap<>(); + private final transient Set> openSockets = new HashSet<>(); - private AFRMIService rmiService = null; + private static final class Externables { + private final AFNaming naming; + private final RMIClientSocketFactory defaultClientFactory; + private final RMIServerSocketFactory defaultServerFactory; - private final Map> openServerSockets = new HashMap<>(); - private final Set> openSockets = new HashSet<>(); + private Externables(AFNaming naming, RMIClientSocketFactory defaultClientFactory, + RMIServerSocketFactory defaultServerFactory) { + this.naming = naming; + this.defaultClientFactory = defaultClientFactory; + this.defaultServerFactory = defaultServerFactory; + } + } /** * Constructor required per definition. - * + * * @see RMISocketFactory */ public AFRMISocketFactory() { - super(); - closeUponRuntimeShutdown(); + this(null, null, null); } /** * Creates a new socket factory. - * + * * @param naming The {@link AFNaming} instance to use. * @param defaultClientFactory The default {@link RMIClientSocketFactory}. * @param defaultServerFactory The default {@link RMIServerSocketFactory}. - * @throws IOException on error. */ @SuppressFBWarnings("EI_EXPOSE_REP2") public AFRMISocketFactory(final AFNaming naming, final RMIClientSocketFactory defaultClientFactory, - final RMIServerSocketFactory defaultServerFactory) throws IOException { + final RMIServerSocketFactory defaultServerFactory) { super(); - this.naming = naming; - this.defaultClientFactory = defaultClientFactory; - this.defaultServerFactory = defaultServerFactory; + this.externables = new Externables(naming, defaultClientFactory, defaultServerFactory); closeUponRuntimeShutdown(); } @@ -106,7 +112,7 @@ public void onRuntimeShutdown(Thread thread) { /** * Creates a new socket address for the given RMI port. - * + * * @param port The port. * @return The socket address. * @throws IOException on error. @@ -115,16 +121,24 @@ public void onRuntimeShutdown(Thread thread) { /** * Creates a new socket that is connected to the given socket address. - * + * * @param addr The socket address. * @return The connected socket. * @throws IOException on error. */ protected abstract AFSocket newConnectedSocket(AFSocketAddress addr) throws IOException; + private synchronized Externables getExternables() { + return externables; + } + + private synchronized void setExternable(Externables externable) { + this.externables = externable; + } + @Override public Socket createSocket(String host, int port) throws IOException { - final RMIClientSocketFactory cf = defaultClientFactory; + final RMIClientSocketFactory cf = getExternables().defaultClientFactory; if (cf != null && port < RMIPorts.AF_PORT_BASE) { return cf.createSocket(host, port); } @@ -145,7 +159,7 @@ public Socket createSocket(String host, int port) throws IOException { @Override public void close() throws IOException { - synchronized (naming) { + synchronized (getExternables().naming) { rmiService = null; closeServerSockets(); closeSockets(); @@ -153,6 +167,7 @@ public void close() throws IOException { } private AFRMIService getRmiService() throws IOException { + AFNaming naming = getExternables().naming; synchronized (naming) { if (rmiService == null) { try { @@ -167,35 +182,98 @@ private AFRMIService getRmiService() throws IOException { /** * Returns a new free port. - * + * * @return The new port. * @throws IOException on error. * @see #returnPort(int) + * @deprecated use {@link #newPortLease()}. */ + @Deprecated protected int newPort() throws IOException { return getRmiService().newPort(); } + /** + * Returns a new free port. + * + * @return The new port, wrapped as a {@link PortLease}. Closing the lease will return the port. + * @throws IOException on error. + */ + protected PortLease newPortLease() throws IOException { + AFRMIService service = getRmiService(); + int port = service.newPort(); + return new PortLease(port, service); + } + /** * Returns a port that was previously returned by {@link #newPort()}. - * + * + * Note that this may call may stall unnecessarily upon shutdown due to locking issues. + * * @param port The port to return. * @throws IOException on error. + * @deprecated use {@link #newPortLease()} */ + @Deprecated protected void returnPort(int port) throws IOException { - getRmiService().returnPort(port); + try { + getRmiService().returnPort(port); + } catch (ShutdownException e) { + // ignore + } catch (IOException e) { + StackTraceUtil.printStackTrace(e); + } + } + + /** + * A lease on a registered port; closing the lease will return the port. + * + * @author Christian Kohlschütter + */ + protected static final class PortLease implements Closeable { + private final int port; + private final AFRMIService rmiService; + + private PortLease(int port, AFRMIService rmiService) { + this.port = port; + this.rmiService = rmiService; + } + + /** + * Closes the lease, returning the port to the {@link AFRMIService} it was leased from. + */ + @Override + public void close() throws IOException { + rmiService.returnPort(getPort()); + } + + /** + * Returns the port number. + * + * @return the port number. + */ + public int getPort() { + return port; + } + + /** + * Returns the service the port was leased from. + * + * @return The service. + */ + public AFRMIService getRmiService() { + return rmiService; + } } @Override public ServerSocket createServerSocket(int port) throws IOException { if (port == 0) { - port = newPort(); - final int returnPort = port; + PortLease portLease = newPortLease(); + port = portLease.getPort(); final AFSocketAddress addr = newSocketAddress(port); AFServerSocket ass = addr.getAddressFamily().newServerSocket(); - ass.addCloseable(() -> { - returnPort(returnPort); - }); + ass.addCloseable(portLease); ass.setReuseAddress(true); ass.setDeleteOnClose(true); ass.bind(addr); @@ -206,7 +284,7 @@ public ServerSocket createServerSocket(int port) throws IOException { return ass; } - final RMIServerSocketFactory sf = defaultServerFactory; + final RMIServerSocketFactory sf = getExternables().defaultServerFactory; if (sf != null && port < RMIPorts.AF_PORT_BASE) { return sf.createServerSocket(port); } @@ -284,21 +362,23 @@ public void close() throws IOException { @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - naming = readNamingInstance(in); - defaultClientFactory = (RMIClientSocketFactory) in.readObject(); - defaultServerFactory = (RMIServerSocketFactory) in.readObject(); + setExternable(new Externables(readNamingInstance(in), // + (RMIClientSocketFactory) in.readObject(), // + (RMIServerSocketFactory) in.readObject())); } @Override public void writeExternal(ObjectOutput out) throws IOException { - writeNamingInstance(out, naming); - out.writeObject(defaultClientFactory); - out.writeObject(defaultServerFactory); + Externables ext = getExternables(); + + writeNamingInstance(out, ext.naming); + out.writeObject(ext.defaultClientFactory); + out.writeObject(ext.defaultServerFactory); } /** * Deserializes information necessary to instantiate the {@link AFNaming} instance. - * + * * @param in The stream. * @return The {@link AFNaming} instance. * @throws IOException on error. @@ -307,7 +387,7 @@ public void writeExternal(ObjectOutput out) throws IOException { /** * Serializes information necessary to instantiate the given {@link AFNaming} instance. - * + * * @param out The stream. * @param namingInstance The {@link AFNaming} instance. * @throws IOException on error. @@ -317,7 +397,7 @@ protected abstract void writeNamingInstance(ObjectOutput out, AFNaming namingIns /** * Checks if the given port refers to a local server port. - * + * * @param port The port to check. * @return {@code true} if the given port is a local server. */ @@ -332,16 +412,16 @@ public boolean isLocalServer(int port) { /** * The naming instance. - * + * * @return The instance. */ protected AFNaming getNaming() { - return naming; + return getExternables().naming; } /** * Checks if this socket factory has some knowledge about the given port. - * + * * @param port The port. * @return {@code true} if registered. */ diff --git a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRegistry.java b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRegistry.java index c2402bb0b..29352029e 100644 --- a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRegistry.java +++ b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRegistry.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import org.newsclub.net.unix.rmi.ShutdownHookSupport.ShutdownHook; @@ -39,7 +40,7 @@ /** * A wrapper for RMI registries, both remote and local, to allow for a clean removal of bound * resources upon shutdown. - * + * * @author Christian Kohlschütter */ public abstract class AFRegistry implements Registry { @@ -48,9 +49,11 @@ public abstract class AFRegistry implements Registry { private final Registry impl; private final Map bound = new HashMap<>(); private final AFNaming naming; - private boolean boundCloserExported = false; + private final AtomicBoolean boundCloserExported = new AtomicBoolean(false); + + private AFRMIService rmiService = null; - AFRegistry(AFNaming naming, Registry impl) throws RemoteException { + AFRegistry(AFNaming naming, Registry impl) { this.naming = naming; this.impl = impl; this.boundCloser = new RemoteCloseable() { @@ -78,7 +81,7 @@ public void onRuntimeShutdown(Thread thread) { /** * Returns {@code true} if the wrapped Registry instance is a locally created * {@link RemoteServer}. - * + * * @return {@code true} if wrapped instance is a locally created {@link RemoteServer}. * @see #isLocal() */ @@ -89,7 +92,7 @@ public boolean isRemoteServer() { /** * Returns {@code true} if the wrapped Registry instance is locally created. - * + * * @return {@code true} if wrapped instance is locally created. */ public final boolean isLocal() { @@ -98,7 +101,7 @@ public final boolean isLocal() { /** * Returns the {@link AFNaming} instance responsible for this registry. - * + * * @return The {@link AFNaming} instance. */ @SuppressFBWarnings("EI_EXPOSE_REP") @@ -246,12 +249,10 @@ private void checkBound() throws RemoteException { empty = bound.isEmpty(); } if (empty) { - if (boundCloserExported) { - boundCloserExported = false; - + if (boundCloserExported.compareAndSet(true, false)) { AFRMIService service; try { - service = naming.getRMIService(this); + service = getRMIService(); service.unregisterForShutdown(boundCloser); } catch (NoSuchObjectException | NotBoundException e) { return; @@ -259,17 +260,23 @@ private void checkBound() throws RemoteException { AFNaming.unexportObject(boundCloser); } } - } else if (!boundCloserExported) { + } else if (boundCloserExported.compareAndSet(false, true)) { AFNaming.exportObject(boundCloser, naming.getSocketFactory()); - boundCloserExported = true; AFRMIService service; try { - service = naming.getRMIService(this); + service = getRMIService(); } catch (NotBoundException e) { return; } service.registerForShutdown(boundCloser); } } + + private AFRMIService getRMIService() throws RemoteException, NotBoundException { + if (rmiService == null) { + rmiService = naming.getRMIService(this); + } + return rmiService; + } } diff --git a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRegistryAccess.java b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRegistryAccess.java index 24d08c92f..0bc4d5ee9 100644 --- a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRegistryAccess.java +++ b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFRegistryAccess.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,9 +28,9 @@ abstract class AFRegistryAccess { /** * Returns a reference to the existing RMI registry. - * + * * If there's no registry running at this port, an exception is thrown. - * + * * @return The registry. * @throws RemoteException If there was a problem. */ @@ -38,7 +38,7 @@ abstract class AFRegistryAccess { /** * Convenience method for {@code getRegistry().lookup}. - * + * * @param name the name for the remote reference to look up * @return The instance * @throws NotBoundException upon error @@ -53,7 +53,7 @@ public Remote lookup(String name) throws NotBoundException, MalformedURLExceptio /** * Convenience method for {@code getRegistry().lookup}. - * + * * @param name the name for the remote reference to look up * @param timeout The timeout value. * @param unit The timeout unit. @@ -70,7 +70,7 @@ public Remote lookup(String name, long timeout, TimeUnit unit) throws NotBoundEx /** * Convenience method for {@code getRegistry().unbind}. - * + * * @param name the name for the remote reference to unbind * @throws RemoteException upon error * @throws NotBoundException upon error @@ -83,7 +83,7 @@ public void unbind(String name) throws RemoteException, NotBoundException, Malfo /** * Convenience method for {@code getRegistry().bind}. - * + * * @param name the name for the remote reference to bind * @param obj the remote reference to bind * @throws RemoteException upon error @@ -98,7 +98,7 @@ public void bind(String name, Remote obj) throws AlreadyBoundException, Malforme /** * Convenience method for {@code getRegistry().rebind}. - * + * * @param name the name for the remote reference to rebind * @param obj the remote reference to rebind * @throws RemoteException upon error @@ -111,10 +111,10 @@ public void rebind(String name, Remote obj) throws MalformedURLException, Remote /** * Convenience method for {@code getRegistry().list}. - * + * * Unlike {@link AFRegistry#list()}, in case the registry has been shut down already, an empty * array is returned. - * + * * @return an array of the names bound in this registry * @throws RemoteException upon error * @throws AccessException upon error diff --git a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFUNIXNaming.java b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFUNIXNaming.java index 10566abef..829aea284 100644 --- a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFUNIXNaming.java +++ b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFUNIXNaming.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,7 @@ /** * The {@link AFUNIXSocket}-compatible equivalent of {@link Naming}. Use this class for accessing * RMI registries that are reachable by {@link AFUNIXSocket}s. - * + * * @author Christian Kohlschütter */ public final class AFUNIXNaming extends AFNaming { @@ -66,10 +66,10 @@ private AFUNIXNaming(File socketDir, int registryPort, String socketPrefix, Stri /** * Returns the directory where RMI sockets are stored by default. - * + * * You can configure this location by setting the System property * {@code org.newsclub.net.unix.rmi.socketdir} upon start. - * + * * @return The directory. */ public static File getDefaultSocketDirectory() { @@ -79,7 +79,7 @@ public static File getDefaultSocketDirectory() { /** * Returns a new private instance that resides in a custom location, to avoid any collisions with * existing instances. - * + * * @return The private {@link AFNaming} instance. * @throws IOException if the operation fails. */ @@ -98,7 +98,7 @@ public static AFUNIXNaming newPrivateInstance() throws IOException { /** * Returns the default instance of {@link AFUNIXNaming}. Sockets are stored in * java.io.tmpdir. - * + * * @return The default instance. * @throws IOException if the operation fails. */ @@ -109,7 +109,7 @@ public static AFUNIXNaming getInstance() throws IOException { /** * Returns a {@link AFUNIXNaming} instance which support several socket files that can be stored * under the same, given directory. - * + * * @param socketDir The directory to store sockets in. * @return The instance. * @throws RemoteException if the operation fails. @@ -121,10 +121,10 @@ public static AFUNIXNaming getInstance(final File socketDir) throws RemoteExcept /** * Returns a {@link AFUNIXNaming} instance which support several socket files that can be stored * under the same, given directory. - * + * * A custom "registry port" can be specified. Typically, AF-UNIX specific ports should be above * {@code 100000}. - * + * * @param socketDir The directory to store sockets in. * @param registryPort The registry port. Should be above {@code 100000}. * @return The instance. @@ -138,10 +138,10 @@ public static AFUNIXNaming getInstance(File socketDir, final int registryPort) /** * Returns a {@link AFUNIXNaming} instance which support several socket files that can be stored * under the same, given directory. - * + * * A custom "registry port" can be specified. Typically, AF-UNIX specific ports should be above * {@code 100000}. - * + * * @param socketDir The directory to store sockets in. * @param registryPort The registry port. Should be above {@code 100000}. * @param socketPrefix A string to be inserted at the beginning of each socket filename, or @@ -183,7 +183,7 @@ public AFUNIXNaming newInstance(int port) throws IOException { /** * Returns an {@link AFUNIXNaming} instance which only supports one file. (Probably only useful * when you want/can access the exported {@link UnicastRemoteObject} directly) - * + * * @param socketFile The socket file. * @return The instance. * @throws IOException if the operation fails. @@ -221,7 +221,7 @@ public AFUNIXRegistry createRegistry() throws RemoteException { * Returns the socket file which is used to control the RMI registry. * * The file is usually in the directory returned by {@link #getRegistrySocketDir()}. - * + * * @return The directory. */ public File getRegistrySocketFile() { @@ -285,7 +285,7 @@ protected void shutdownRegistryFinishingTouches() { /** * Returns the directory in which sockets used by this registry are located. - * + * * @return The directory. */ public File getRegistrySocketDir() { diff --git a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFUNIXRMISocketFactory.java b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFUNIXRMISocketFactory.java index 4f101db97..e38e40a32 100644 --- a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFUNIXRMISocketFactory.java +++ b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFUNIXRMISocketFactory.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,7 +39,7 @@ /** * An {@link RMISocketFactory} that supports {@link AFUNIXSocket}s. - * + * * @author Christian Kohlschütter */ public class AFUNIXRMISocketFactory extends AFRMISocketFactory { @@ -52,11 +52,11 @@ public class AFUNIXRMISocketFactory extends AFRMISocketFactory { private String socketPrefix; private String socketSuffix; - private final Map credentials = new HashMap<>(); + private final transient Map credentials = new HashMap<>(); /** * Constructor required per definition. - * + * * @see RMISocketFactory */ public AFUNIXRMISocketFactory() { @@ -65,7 +65,7 @@ public AFUNIXRMISocketFactory() { /** * Creates a new socket factory. - * + * * @param naming The {@link AFNaming} instance to use. * @param socketDir The directory to store the sockets in. * @param defaultClientFactory The default {@link RMIClientSocketFactory}. @@ -74,13 +74,12 @@ public AFUNIXRMISocketFactory() { * {@code null}. * @param socketSuffix A string that will be added to the end of each socket filename, or * {@code null}. - * @throws IOException on error. */ @SuppressFBWarnings("EI_EXPOSE_REP2") public AFUNIXRMISocketFactory(final AFNaming naming, final File socketDir, final RMIClientSocketFactory defaultClientFactory, final RMIServerSocketFactory defaultServerFactory, final String socketPrefix, - final String socketSuffix) throws IOException { + final String socketSuffix) { super(naming, defaultClientFactory, defaultServerFactory); Objects.requireNonNull(socketDir); this.socketDir = socketDir; @@ -90,27 +89,24 @@ public AFUNIXRMISocketFactory(final AFNaming naming, final File socketDir, /** * Creates a new socket factory. - * + * * @param naming The {@link AFNaming} instance to use. * @param socketDir The directory to store the sockets in. * @param defaultClientFactory The default {@link RMIClientSocketFactory}. * @param defaultServerFactory The default {@link RMIServerSocketFactory}. - * @throws IOException on error. */ public AFUNIXRMISocketFactory(AFNaming naming, File socketDir, - RMIClientSocketFactory defaultClientFactory, RMIServerSocketFactory defaultServerFactory) - throws IOException { + RMIClientSocketFactory defaultClientFactory, RMIServerSocketFactory defaultServerFactory) { this(naming, socketDir, defaultClientFactory, defaultServerFactory, null, null); } /** * Creates a new socket factory. - * + * * @param naming The {@link AFNaming} instance to use. * @param socketDir The directory to store the sockets in. - * @throws IOException on error. */ - public AFUNIXRMISocketFactory(AFNaming naming, File socketDir) throws IOException { + public AFUNIXRMISocketFactory(AFNaming naming, File socketDir) { this(naming, socketDir, DefaultRMIClientSocketFactory.getInstance(), DefaultRMIServerSocketFactory.getInstance()); } @@ -146,7 +142,7 @@ public void writeExternal(ObjectOutput out) throws IOException { @Override public int hashCode() { - return socketDir == null ? super.hashCode() : socketDir.hashCode(); + return socketDir == null ? System.identityHashCode(this) : socketDir.hashCode(); } @Override @@ -164,7 +160,7 @@ public boolean equals(Object other) { /** * The directory in which socket files are stored. - * + * * @return The directory. */ public File getSocketDir() { @@ -222,7 +218,9 @@ public String toString() { @Override public void close() throws IOException { - credentials.clear(); + synchronized (credentials) { + credentials.clear(); + } super.close(); } diff --git a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFUNIXRegistry.java b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFUNIXRegistry.java index 7f94a6042..7c76fac26 100644 --- a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFUNIXRegistry.java +++ b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/AFUNIXRegistry.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,19 +17,18 @@ */ package org.newsclub.net.unix.rmi; -import java.rmi.RemoteException; import java.rmi.registry.Registry; /** * A wrapper for RMI registries, both remote and local, to allow for a clean removal of bound * resources upon shutdown. - * + * * This subclass mostly exists for backwards compatibility. - * + * * @author Christian Kohlschütter */ public class AFUNIXRegistry extends AFRegistry { - AFUNIXRegistry(AFNaming naming, Registry impl) throws RemoteException { + AFUNIXRegistry(AFNaming naming, Registry impl) { super(naming, impl); } } diff --git a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/DefaultRMIClientSocketFactory.java b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/DefaultRMIClientSocketFactory.java index a7b3cb4ca..815d846f1 100644 --- a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/DefaultRMIClientSocketFactory.java +++ b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/DefaultRMIClientSocketFactory.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,15 +22,17 @@ import java.net.Socket; import java.rmi.server.RMIClientSocketFactory; -import org.newsclub.net.unix.AFSocket; +import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; /** * An implementation of {@link RMIClientSocketFactory}. - * - * @see AFRMISocketFactory where we deal with {@link AFSocket}s. + * + * @see AFRMISocketFactory */ +@SuppressFBWarnings("SING_SINGLETON_IMPLEMENTS_SERIALIZABLE") public final class DefaultRMIClientSocketFactory implements RMIClientSocketFactory, Serializable { private static final long serialVersionUID = 1L; + private static final DefaultRMIClientSocketFactory INSTANCE = new DefaultRMIClientSocketFactory(); private DefaultRMIClientSocketFactory() { @@ -38,7 +40,7 @@ private DefaultRMIClientSocketFactory() { /** * Returns the singleton instance for DefaultRMIClientSocketFactory. - * + * * @return The singleton. */ public static DefaultRMIClientSocketFactory getInstance() { diff --git a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/DefaultRMIServerSocketFactory.java b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/DefaultRMIServerSocketFactory.java index 55c688763..c847d2895 100644 --- a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/DefaultRMIServerSocketFactory.java +++ b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/DefaultRMIServerSocketFactory.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,13 +22,15 @@ import java.net.ServerSocket; import java.rmi.server.RMIServerSocketFactory; -import org.newsclub.net.unix.AFSocket; +import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; /** * An implementation of {@link RMIServerSocketFactory}. - * - * @see AFRMISocketFactory where we deal with {@link AFSocket}. + * + * @see AFRMISocketFactory */ +@SuppressFBWarnings({ + "SING_SINGLETON_IMPLEMENTS_SERIALIZABLE", "SING_SINGLETON_HAS_NONPRIVATE_CONSTRUCTOR"}) public class DefaultRMIServerSocketFactory implements RMIServerSocketFactory, Serializable { private static final long serialVersionUID = 1L; private static final DefaultRMIServerSocketFactory INSTANCE = new DefaultRMIServerSocketFactory(); @@ -42,7 +44,7 @@ public DefaultRMIServerSocketFactory() { /** * Returns the singleton instance for DefaultRMIServerSocketFactory. - * + * * @return The singleton. */ public static DefaultRMIServerSocketFactory getInstance() { diff --git a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RMIPorts.java b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RMIPorts.java index 246e302c9..e15b62036 100644 --- a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RMIPorts.java +++ b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RMIPorts.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ /** * Contains some default ports used by junixsocket for RMI-over-AF_UNIX etc. - * + * * @author Christian Kohlschütter * @see AFRMISocketFactory * @see AFRMIService @@ -42,14 +42,14 @@ final class RMIPorts { /** * This is the port reserved for the port assigner. - * + * * @see AFRMIService */ static final int RMI_SERVICE_PORT = 100002; /** * This is the base for anonymous ports. Any anonymous port will be higher than this number. - * + * * @see AFRMIService */ static final int ANONYMOUS_PORT_BASE = 110000; @@ -60,7 +60,7 @@ final class RMIPorts { */ static final int PLAIN_FILE_SOCKET = Integer.MAX_VALUE; - @ExcludeFromCodeCoverageGeneratedReport + @ExcludeFromCodeCoverageGeneratedReport(reason = "unreachable") private RMIPorts() { throw new UnsupportedOperationException("No instances"); } diff --git a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteCloseable.java b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteCloseable.java index 54aa9fc48..94b3c4409 100644 --- a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteCloseable.java +++ b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteCloseable.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,17 +24,18 @@ /** * A resource that can be exposed remotely, and closed locally as well as remotely. - * + * * @author Christian Kohlschütter + * @param The resource type. */ public interface RemoteCloseable extends Remote, Closeable { /** * Returns the resource (or the Remote instance of it). - * + * * If the returned resource is {@link Closeable}, then closing via {@code get().close()}} will * affect the client-side (local), but not necessarily the server-side as well (the exact behavior * depends on the resource). - * + * * @return The wrapped resource. * @throws NoSuchObjectException if this instance has been closed already. * @throws IOException if there was a problem. @@ -44,7 +45,7 @@ public interface RemoteCloseable extends Remote, Closeable { /** * Closes the resource on the server-side (i.e., where it was created), and — as long as the * wrapped resource returned by {@link #get()} supports it — locally as well. - * + * * @throws IOException if there was a problem. */ @Override diff --git a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteCloseableImpl.java b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteCloseableImpl.java index 2eb581474..7601b2657 100644 --- a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteCloseableImpl.java +++ b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteCloseableImpl.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,13 +27,14 @@ * * @author Christian Kohlschütter * @see RemoteCloseable + * @param The resource type. */ -public class RemoteCloseableImpl implements RemoteCloseable { +public final class RemoteCloseableImpl implements RemoteCloseable { private final T remote; /** * Created a new instance. - * + * * @param socketFactory The socket factory. * @param obj The object to wrap. * @throws RemoteException on error. @@ -44,18 +45,18 @@ public RemoteCloseableImpl(RMISocketFactory socketFactory, T obj) throws RemoteE } @Override - public final void close() throws IOException { + public void close() throws IOException { AFNaming.unexportObject(this); doClose(remote); } /** * Closes the given object. - * + * * @param obj The object to close. * @throws IOException on error. */ - protected void doClose(T obj) throws IOException { + private void doClose(T obj) throws IOException { if (obj instanceof Closeable) { ((Closeable) obj).close(); } diff --git a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteFileDescriptor.java b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteFileDescriptor.java index 42406fe4f..26e3875c8 100644 --- a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteFileDescriptor.java +++ b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteFileDescriptor.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ /** * A generic (unspecific) {@link FileDescriptor} reference. - * + * * @author Christian Kohlschütter * @see RemoteFileInput * @see RemoteFileOutput @@ -34,7 +34,7 @@ public final class RemoteFileDescriptor extends RemoteFileDescriptorBase { /** * Creates an uninitialized instance; used for externalization. - * + * * @see #readExternal(ObjectInput) */ public RemoteFileDescriptor() { @@ -44,7 +44,7 @@ public RemoteFileDescriptor() { /** * Creates a new {@link RemoteFileOutput} instance, encapsulating a generic {@link FileDescriptor} * so that it can be shared with other processes via RMI. - * + * * @param socketFactory The socket factory. * @param fd The {@link FileDescriptor}. */ @@ -56,7 +56,7 @@ public RemoteFileDescriptor(AFUNIXRMISocketFactory socketFactory, FileDescriptor public synchronized void close() throws IOException { FileDescriptor fd = getFileDescriptor(); if (fd != null && fd.valid()) { - try (FileInputStream fin = new FileInputStream(fd)) { + try (FileInputStream unused = new FileInputStream(fd)) { // should succeed } } diff --git a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteFileDescriptorBase.java b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteFileDescriptorBase.java index 32a4a1224..a3d9659dd 100644 --- a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteFileDescriptorBase.java +++ b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteFileDescriptorBase.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,8 +22,6 @@ import java.io.DataOutputStream; import java.io.Externalizable; import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInput; @@ -31,6 +29,7 @@ import java.io.OutputStream; import java.net.SocketException; import java.util.Objects; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -46,10 +45,11 @@ /** * A wrapper that allows a {@link FileDescriptor} be sent via RMI over AF_UNIX sockets. - * + * * @author Christian Kohlschütter - * @see RemoteFileInput subclass for sending {@link FileInputStream}s. - * @see RemoteFileOutput subclass for sending {@link FileOutputStream}s. + * @param The resource type. + * @see RemoteFileInput + * @see RemoteFileOutput */ public abstract class RemoteFileDescriptorBase implements Externalizable, Closeable, FileDescriptorAccess { @@ -75,22 +75,22 @@ public abstract class RemoteFileDescriptorBase implements Externalizable, Clo /** * An optional, closeable resource that is related to this instance. If the reference is non-null, * this will be closed upon {@link #close()}. - * + * * For unidirectional implementations, this could be the corresponding input/output stream. For * bidirectional implementations (e.g., a Socket, Pipe, etc.), this should close both directions. */ protected final transient AtomicReference resource = new AtomicReference<>(); private int magicValue; - private FileDescriptor fd; + private transient FileDescriptor fd; private AFUNIXRMISocketFactory socketFactory; /** * Creates an uninitialized instance; used for externalization. - * + * * @see #readExternal(ObjectInput) */ - RemoteFileDescriptorBase() { + public RemoteFileDescriptorBase() { } RemoteFileDescriptorBase(AFUNIXRMISocketFactory socketFactory, T stream, FileDescriptor fd, @@ -153,7 +153,9 @@ protected void onServerStopped(AFServerSocket socket) { } }; - server.startThenStopAfter(SERVER_TIMEOUT, TimeUnit.MILLISECONDS); + @SuppressWarnings("unused") + ScheduledFuture unused = server.startThenStopAfter(SERVER_TIMEOUT, + TimeUnit.MILLISECONDS); } catch (IOException e) { objOut.writeObject(e); throw e; @@ -223,10 +225,10 @@ public final void readExternal(ObjectInput objIn) throws IOException, ClassNotFo /** * Returns the file descriptor. - * + * * This is either the original one that was specified in the constructor or a copy that was sent * via RMI over an AF_UNIX connection as part of an ancillary message. - * + * * @return The file descriptor. */ @Override @@ -237,11 +239,11 @@ public final FileDescriptor getFileDescriptor() { /** * Returns the "magic value" for this type of file descriptor. - * + * * The magic value consists of an indicator ("this is a file descriptor") as well as its * capabilities (read/write). It is used to prevent, for example, converting an output stream to * an input stream. - * + * * @return The magic value. */ protected final int getMagicValue() { diff --git a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteFileInput.java b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteFileInput.java index bad662be2..dd0784674 100644 --- a/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteFileInput.java +++ b/junixsocket-rmi/src/main/java/org/newsclub/net/unix/rmi/RemoteFileInput.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ /** * A specialized subclass of {@link RemoteFileDescriptorBase}, specifically for * {@link FileInputStream}s. - * + * * @author Christian Kohlschütter */ public final class RemoteFileInput extends RemoteFileDescriptorBase implements @@ -34,7 +34,7 @@ public final class RemoteFileInput extends RemoteFileDescriptorBase { @@ -33,7 +33,7 @@ public final class RemoteFileOutput extends RemoteFileDescriptorBase list; @@ -68,19 +69,19 @@ static void runHooks() { /** * Something that wants to be called upon Runtime shutdown. - * + * * @author Christian Kohlschütter */ interface ShutdownHook { /** * Called upon Runtime shutdown. - * + * * When you implement this method, make sure to check that the given Thread matches the current * thread, e.g.: * if (thread != Thread.currentThread() || !(thread instanceof ShutdownThread)) { * throw new IllegalStateException("Illegal caller"); } * - * + * * @param thread The current Thread. * @throws Exception Most likely ignored */ @@ -90,7 +91,7 @@ interface ShutdownHook { /** * The Thread that will be called upon Runtime shutdown. - * + * * @author Christian Kohlschütter */ static final class ShutdownThread extends Thread { diff --git a/junixsocket-rmi/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-rmi/proxy-config.json b/junixsocket-rmi/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-rmi/proxy-config.json new file mode 100644 index 000000000..79d3e5ff8 --- /dev/null +++ b/junixsocket-rmi/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-rmi/proxy-config.json @@ -0,0 +1,8 @@ +[ + { + "interfaces":["org.newsclub.net.unix.rmi.AFRMIService"] + }, + { + "interfaces":["org.newsclub.net.unix.rmi.RemoteCloseable"] + } +] \ No newline at end of file diff --git a/junixsocket-rmi/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-rmi/reflect-config.json b/junixsocket-rmi/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-rmi/reflect-config.json new file mode 100644 index 000000000..7527aeb6e --- /dev/null +++ b/junixsocket-rmi/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-rmi/reflect-config.json @@ -0,0 +1,121 @@ +[ +{ + "name":"[Ljava.rmi.server.ObjID;" +}, +{ + "name":"java.rmi.NoSuchObjectException" +}, +{ + "name":"java.rmi.Remote", + "queryAllPublicMethods":true +}, +{ + "name":"java.rmi.RemoteException" +}, +{ + "name":"java.rmi.dgc.Lease" +}, +{ + "name":"java.rmi.dgc.VMID" +}, +{ + "name":"java.rmi.registry.Registry", + "queryAllPublicMethods":true +}, +{ + "name":"java.rmi.NoSuchObjectException" +}, +{ + "name":"java.rmi.Remote", + "queryAllPublicMethods":true +}, +{ + "name":"java.rmi.RemoteException" +}, +{ + "name":"java.rmi.dgc.Lease" +}, +{ + "name":"java.rmi.dgc.VMID" +}, +{ + "name":"java.rmi.registry.Registry", + "queryAllPublicMethods":true +}, +{ + "name":"java.rmi.server.ObjID" +}, +{ + "name":"java.rmi.server.RemoteObject" +}, +{ + "name":"java.rmi.server.RemoteObjectInvocationHandler" +}, +{ + "name":"java.rmi.server.UID" +}, +{ + "name":"org.newsclub.net.unix.rmi.AFRMIService", + "queryAllPublicMethods":true +}, +{ + "name":"org.newsclub.net.unix.rmi.AFRMIServiceImpl_Stub" +}, +{ + "name":"org.newsclub.net.unix.rmi.AFRMISocketFactory" +}, +{ + "name":"org.newsclub.net.unix.rmi.AFRegistry$1_Stub" +}, +{ + "name":"org.newsclub.net.unix.rmi.AFSocketCapabilityCondition", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.rmi.AFSocketCapabilityRequirement", + "queryAllPublicMethods":true +}, +{ + "name":"org.newsclub.net.unix.rmi.AFUNIXRMISocketFactory" +}, +{ + "name":"org.newsclub.net.unix.rmi.RemoteCloseable", + "queryAllPublicMethods":true, + "methods":[{"name":"close","parameterTypes":[] }, {"name":"get","parameterTypes":[] }] +}, +{ + "name":"org.newsclub.net.unix.rmi.RemoteCloseableImpl_Stub" +}, +{ + "name":"org.newsclub.net.unix.rmi.RemoteFileDescriptor" +}, +{ + "name":"org.newsclub.net.unix.rmi.RemoteFileDescriptorBase" +}, +{ + "name":"org.newsclub.net.unix.rmi.RemoteFileInput" +}, +{ + "name":"org.newsclub.net.unix.rmi.RemoteFileOutput" +}, +{ + "name":"sun.rmi.registry.RegistryImpl_Skel", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.rmi.registry.RegistryImpl_Stub", + "methods":[{"name":"","parameterTypes":["java.rmi.server.RemoteRef"] }] +}, +{ + "name":"sun.rmi.server.UnicastRef2", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.rmi.transport.DGCImpl_Skel", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.rmi.transport.DGCImpl_Stub", + "methods":[{"name":"","parameterTypes":["java.rmi.server.RemoteRef"] }] +} +] \ No newline at end of file diff --git a/junixsocket-rmi/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-rmi/resource-config.json b/junixsocket-rmi/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-rmi/resource-config.json new file mode 100644 index 000000000..f3700875f --- /dev/null +++ b/junixsocket-rmi/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-rmi/resource-config.json @@ -0,0 +1,9 @@ +{ + "resources":{ + "includes":[ + { + "pattern":"\\QMETA-INF/services/java.rmi.server.RMIClassLoaderSpi\\E" + } + ]}, + "bundles":[] +} \ No newline at end of file diff --git a/junixsocket-rmi/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-rmi/serialization-config.json b/junixsocket-rmi/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-rmi/serialization-config.json new file mode 100644 index 000000000..4be702647 --- /dev/null +++ b/junixsocket-rmi/src/main/resources/META-INF/native-image/com.kohlschutter.junixsocket/junixsocket-rmi/serialization-config.json @@ -0,0 +1,80 @@ +{ + "types": [ + { + "name": "java.rmi.NoSuchObjectException" + }, + { + "name": "java.rmi.RemoteException" + }, + { + "name": "java.rmi.dgc.Lease" + }, + { + "name": "java.rmi.dgc.VMID" + }, + { + "name": "java.rmi.server.ObjID" + }, + { + "name": "java.rmi.server.ObjID[]" + }, + { + "name": "java.rmi.server.RemoteObject" + }, + { + "name": "java.rmi.server.RemoteObjectInvocationHandler" + }, + { + "name": "java.rmi.server.UID" + }, + { + "name": "org.newsclub.net.unix.AFUNIXSocketCredentials" + }, + { + "name": "org.newsclub.net.unix.rmi.AFRMISocketFactory" + }, + { + "name": "org.newsclub.net.unix.rmi.AFUNIXRMISocketFactory" + }, + { + "name": "org.newsclub.net.unix.rmi.RemoteCloseableThing$IsCloseable" + }, + { + "name": "org.newsclub.net.unix.rmi.RemoteCloseableThing$NotCloseable" + }, + { + "name": "org.newsclub.net.unix.rmi.RemoteCloseableThingImpl" + }, + { + "name": "org.newsclub.net.unix.rmi.RemoteCloseableThingImpl$IsCloseableImpl" + }, + { + "name": "org.newsclub.net.unix.rmi.RemoteCloseableThingImpl$NotCloseableImpl" + }, + { + "name": "org.newsclub.net.unix.rmi.RemoteFileDescriptor" + }, + { + "name": "org.newsclub.net.unix.rmi.RemoteFileDescriptorBase" + }, + { + "name": "org.newsclub.net.unix.rmi.RemoteFileInput" + }, + { + "name": "org.newsclub.net.unix.rmi.RemoteFileOutput" + } + ], + "lambdaCapturingTypes": [], + "proxies": [ + { + "interfaces": [ + "org.newsclub.net.unix.rmi.NaiveFileInputStreamRemote" + ] + }, + { + "interfaces": [ + "org.newsclub.net.unix.rmi.RemoteCloseable" + ] + } + ] +} \ No newline at end of file diff --git a/junixsocket-rmi/src/site/site.xml b/junixsocket-rmi/src/site/site.xml index 873972284..6d13afab6 100644 --- a/junixsocket-rmi/src/site/site.xml +++ b/junixsocket-rmi/src/site/site.xml @@ -7,6 +7,7 @@ org.apache.maven.skins maven-fluido-skin + 2.0.0-M8 diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/AFSocketCapabilityCondition.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/AFSocketCapabilityCondition.java index 92d875e16..470fb3960 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/AFSocketCapabilityCondition.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/AFSocketCapabilityCondition.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,12 @@ // This is a deliberate copy of the same class from junixsocket-common's tests. // CPD-OFF public class AFSocketCapabilityCondition implements ExecutionCondition { + /** + * Constructs a new {@link AFSocketCapabilityCondition}. + */ + public AFSocketCapabilityCondition() { + } + @SuppressWarnings("exports") @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/AFSocketCapabilityRequirement.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/AFSocketCapabilityRequirement.java index 7ba3a0c30..d0e29172f 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/AFSocketCapabilityRequirement.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/AFSocketCapabilityRequirement.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/Hello.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/Hello.java index f8dca96f1..8e176a0c8 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/Hello.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/Hello.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,13 +22,13 @@ /** * A very simple "hello" service. - * + * * @author Christian Kohlschütter */ public interface Hello extends Remote { /** * Returns "Hello". - * + * * @return "Hello" * @throws IOException if the operation fails. */ diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/HelloImpl.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/HelloImpl.java index 813f61658..dc1a3a615 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/HelloImpl.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/HelloImpl.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ /** * The implementation of the very simple {@link Hello} service. - * + * * @author Christian Kohlschütter */ public class HelloImpl implements Hello { diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/JunixsocketVersionTest.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/JunixsocketVersionTest.java index b8c4092ab..e67809073 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/JunixsocketVersionTest.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/JunixsocketVersionTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,24 +18,22 @@ package org.newsclub.net.unix.rmi; import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.Test; import org.newsclub.net.unix.AFSocket; /** * Tests whether we can get the junixsocket version (which is encoded in a properties file). - * + * * This can't easily be tested from within the Maven build for junixsocket-common, which is why we * have it in junixsocket-rmi. - * + * * @author Christian Kohlschütter */ public class JunixsocketVersionTest { @Test public void testVersion() { String version = AFSocket.getVersion(); - assertNotNull(version); assertNotEquals("", version); } } diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/NaiveFileInputStreamRemote.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/NaiveFileInputStreamRemote.java index 57575f843..0d282bac5 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/NaiveFileInputStreamRemote.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/NaiveFileInputStreamRemote.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,10 +25,10 @@ /** * Exposes a {@link FileInputStream}'s basic functionality over RMI. - * + * * As opposed to {@link RemoteFileDescriptorBase}, all data is read, then copied and serialized via * RMI. - * + * * @author Christian Kohlschütter * @see RemoteFileDescriptorBase for a better way. */ diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/NaiveFileInputStreamRemoteImpl.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/NaiveFileInputStreamRemoteImpl.java index 36090a9db..97a528c3b 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/NaiveFileInputStreamRemoteImpl.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/NaiveFileInputStreamRemoteImpl.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RMIPeerCredentialsTest.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RMIPeerCredentialsTest.java index 5446de81f..1cd5e300d 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RMIPeerCredentialsTest.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RMIPeerCredentialsTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ /** * Verifies that peer credentials are properly set when communicating over RMI. - * + * * @author Christian Kohlschütter */ @AFSocketCapabilityRequirement({ diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RegistryTest.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RegistryTest.java index bbc00fc73..eaf665912 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RegistryTest.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RegistryTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteCloseableTest.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteCloseableTest.java index 961aa72e7..fb890ddf4 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteCloseableTest.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteCloseableTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ package org.newsclub.net.unix.rmi; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.rmi.NoSuchObjectException; @@ -35,7 +35,7 @@ /** * Tests {@link RemoteCloseable}. - * + * * @author Christian Kohlschütter */ @SuppressFBWarnings({ @@ -54,26 +54,31 @@ public void testRemoteCloseableWithACloseableThing() throws IOException, NotBoun try (RemoteCloseable remoteCloseable = svc.remoteCloseable( IsCloseable.class)) { - try (IsCloseable testCloseable = remoteCloseable.get()) { + try (IsCloseable unused = remoteCloseable.get()) { assertEquals(0, svc.remoteCloseableThingNumberOfCloseCalls(IsCloseable.class)); // forcibly calling close here unexports the RemoteCloseable remoteCloseable.close(); assertEquals(1, svc.remoteCloseableThingNumberOfCloseCalls(IsCloseable.class)); - assertThrows(NoSuchObjectException.class, () -> { - remoteCloseable.close(); - }); + remoteCloseable.close(); + fail("Should have thrown an exception"); } } catch (NoSuchObjectException e) { // expected — since the object was forcibly closed above, it was unexported already. // ideally, RMI could gracefully handle calling #close() on an proxy that points to an // unexported object. + } catch (IllegalArgumentException e) { + if (e.getCause() instanceof NoSuchMethodException) { + // observed with GraalVM 17.0.9; see java.rmi.server.RemoteObjectInvocationHandler + } else { + throw e; + } } assertEquals(1, svc.remoteCloseableThingNumberOfCloseCalls(IsCloseable.class)); try (RemoteCloseable remoteCloseable = svc.remoteCloseable( IsCloseable.class)) { - try (IsCloseable testCloseable = remoteCloseable.get()) { + try (IsCloseable unused = remoteCloseable.get()) { // no exception thrown } } @@ -99,18 +104,22 @@ public void testRemoteCloseableWithANotCloseableThing() throws IOException, NotB remoteCloseable.close(); assertEquals(0, svc.remoteCloseableThingNumberOfCloseCalls(NotCloseable.class)); - assertThrows(NoSuchObjectException.class, () -> { - remoteCloseable.close(); - }); + remoteCloseable.close(); + fail("Should have thrown an exception"); } catch (NoSuchObjectException e) { // expected — since the object was forcibly closed above, it was unexported already. // ideally, RMI could gracefully handle calling #close() on an proxy that points to an // unexported object. + } catch (IllegalArgumentException e) { + if (e.getCause() instanceof NoSuchMethodException) { + // observed with GraalVM 17.0.9; see java.rmi.server.RemoteObjectInvocationHandler + } else { + throw e; + } } assertEquals(0, svc.remoteCloseableThingNumberOfCloseCalls(NotCloseable.class)); - try (RemoteCloseable remoteCloseable = svc.remoteCloseable( - NotCloseable.class)) { + try (RemoteCloseable unused = svc.remoteCloseable(NotCloseable.class)) { // no exception thrown } diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteCloseableThing.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteCloseableThing.java index 47b7e7894..6c5ee35a1 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteCloseableThing.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteCloseableThing.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ /** * To be used by {@link RemoteCloseableTest}. - * + * * @author Christian Kohlschütter */ public interface RemoteCloseableThing extends Serializable { diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteCloseableThingImpl.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteCloseableThingImpl.java index a904ca45b..8113dc2f6 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteCloseableThingImpl.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteCloseableThingImpl.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ /** * To be used by {@link RemoteCloseableTest}. - * + * * @author Christian Kohlschütter */ abstract class RemoteCloseableThingImpl implements RemoteCloseableThing { @@ -34,6 +34,11 @@ abstract class RemoteCloseableThingImpl implements RemoteCloseableThing { protected RemoteCloseableThingImpl() throws RemoteException { } + @SuppressWarnings("all") + @Deprecated + protected final void finalize() { + } + public void close() throws IOException { count.incrementAndGet(); } diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteFileDescriptorTest.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteFileDescriptorTest.java index 8cc8b3ffb..74585d7e6 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteFileDescriptorTest.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteFileDescriptorTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import java.nio.charset.StandardCharsets; import java.rmi.NotBoundException; import java.rmi.server.RMISocketFactory; +import java.util.Objects; import org.junit.jupiter.api.Test; import org.newsclub.net.unix.AFSocketCapability; @@ -63,7 +64,7 @@ public void testRemoteStdout() throws IOException, NotBoundException { public void testRemoteStdoutNoop() throws IOException, NotBoundException { TestService svc = lookupTestService(); - try (RemoteFileDescriptor stdout = svc.stdout()) { + try (RemoteFileDescriptor unused = svc.stdout()) { // not doing anything here should trigger descriptor cleanup in RemoteFileDescriptor#close } } @@ -83,7 +84,8 @@ public void testWriteAndReadHello() throws IOException, NotBoundException { } try (NaiveFileInputStreamRemote rfis = svc.naiveInputStreamRemote(); - FileInputStream fin = rfis.getRemoteFileDescriptor().asFileInputStream()) { + FileInputStream fin = Objects.requireNonNull(rfis.getRemoteFileDescriptor() + .asFileInputStream())) { assertEquals('H', rfis.read()); assertEquals('e', fin.read()); assertEquals('l', fin.read()); diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteRegistryTest.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteRegistryTest.java index f6b47ef64..b21d98c03 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteRegistryTest.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/RemoteRegistryTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,35 +42,56 @@ import com.kohlschutter.testutil.ForkedVMRequirement; import com.kohlschutter.testutil.OutputBridge; import com.kohlschutter.testutil.OutputBridge.ProcessStream; +import com.kohlschutter.testutil.TestAsyncUtil; -@ForkedVMRequirement(forkSupported = true) @SuppressFBWarnings({ "THROWS_METHOD_THROWS_CLAUSE_THROWABLE", "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION"}) public class RemoteRegistryTest { @Test + @ForkedVMRequirement(forkSupported = true) public void testRemoteRegistry() throws Exception { File socketDir = tempSocketDir(); + try { + try (SpawnedRegistryAccess sra = new SpawnedRegistryAccess(TestRegistryServer.class + .getSimpleName(), socketDir) { - try (SpawnedRegistryAccess sra = new SpawnedRegistryAccess(TestRegistryServer.class - .getSimpleName(), socketDir) { + @Override + protected int exportHelloDelayMillis() { + return 500; + } - @Override - protected int exportHelloDelayMillis() { - return 500; + }) { + tryToSayHello(sra); + sra.shutdown(); + } catch (Exception e) { + throw e; } - }) { - tryToSayHello(sra); - sra.shutdown(); - } catch (Exception e) { - throw e; + assertEquals(0, countRMIFiles(socketDir), "There shouldn't be any RMI socket files in " + + socketDir); + } finally { + assertTrue(deleteDirectory(socketDir), "Should be able to delete temporary directory: " + + socketDir); } + } - assertEquals(0, countRMIFiles(socketDir)); - assertTrue(socketDir.delete()); + private boolean deleteDirectory(File f) { + if (!f.exists()) { + return true; + } + File[] files = f.listFiles(); + if (files != null) { + for (File d : files) { + if (!d.delete() || d.isDirectory()) { + deleteDirectory(f); + } + } + } + return f.delete(); } @Test + @ForkedVMRequirement(forkSupported = true) public void testRemoteRegistryStandardPath() throws Exception { File socketDir = AFUNIXNaming.getDefaultSocketDirectory(); assumeTrue(countRMIFiles(socketDir) == 0, @@ -96,32 +117,55 @@ protected AFNaming getNamingInstance() throws IOException { } @Test + @ForkedVMRequirement(forkSupported = true) + @SuppressWarnings("PMD.UnusedAssignment") public void testRemoteShutdownNotAllowed() throws Exception { File socketDir = tempSocketDir(); + try { + try (SpawnedRegistryAccess sra = new SpawnedRegistryAccess(TestRegistryServer.class + .getSimpleName(), socketDir) { - try (SpawnedRegistryAccess sra = new SpawnedRegistryAccess(TestRegistryServer.class - .getSimpleName(), socketDir) { + @Override + protected boolean remoteShutdownAllowed() { + return false; + } - @Override - protected boolean remoteShutdownAllowed() { - return false; - } + @Override + protected int shutdownAfterSecs() { + return 5; + } + }) { + AFRegistry registry = sra.getRegistry(); + assertNotNull(registry, "Could not access the AFUNIXRegistry created by the forked VM"); - @Override - protected int shutdownAfterSecs() { - return 30; + assertThrows(ServerException.class, () -> sra.getRegistry().getNaming().shutdownRegistry()); + + sra.shutdownAndWait(false); + if (!awaitNoRMIFiles(socketDir)) { + sra.shutdownAndWait(true); + } + } catch (Exception e) { + throw e; } - }) { - AFRegistry registry = sra.getRegistry(); - assertNotNull(registry, "Could not access the AFUNIXRegistry created by the forked VM"); - assertThrows(ServerException.class, () -> sra.getRegistry().getNaming().shutdownRegistry()); + assertTrue(awaitNoRMIFiles(socketDir), "There shouldn't be any RMI socket files in " + + socketDir); + } finally { + assertTrue(deleteDirectory(socketDir), "Should be able to delete temporary directory: " + + socketDir); + } + } - sra.shutdownAndWait(true); - } catch (Exception e) { - throw e; + private boolean awaitNoRMIFiles(File socketDir) throws InterruptedException { + int count = 0; + for (int i = 0; i < 50; i++) { + count = countRMIFiles(socketDir); + if (count == 0) { + return true; + } + Thread.sleep(100); } - assertEquals(0, countRMIFiles(socketDir)); + return count == 0; } private void tryToSayHello(SpawnedRegistryAccess sra) throws Exception { @@ -176,6 +220,11 @@ private static class SpawnedRegistryAccess implements AutoCloseable { asyncGetRegistry(); } + @SuppressWarnings("all") + @Deprecated + protected final void finalize() { + } + void shutdown() { markedShutdown.set(true); executors.shutdownNow(); @@ -235,7 +284,7 @@ protected int shutdownAfterSecs() { } private void watchProcessAsync() { - executors.submit(() -> { + TestAsyncUtil.runAsync(executors, () -> { try { registryProcess.waitFor(); } catch (InterruptedException e) { @@ -249,12 +298,12 @@ private void watchProcessAsync() { "The spawned VM has terminated with RC=" + registryProcess.exitValue())); } }); - executors.submit(bridgeOut); - executors.submit(bridgeErr); + TestAsyncUtil.runAsync(executors, bridgeOut); + TestAsyncUtil.runAsync(executors, bridgeErr); } private void asyncGetRegistry() { - executors.submit(() -> { + TestAsyncUtil.runAsync(executors, () -> { try { AFNaming naming = getNamingInstance(); AFRegistry registry = naming.getRegistry(30, TimeUnit.SECONDS); diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/SelftestProvider.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/SelftestProvider.java index 263ce5b39..7c844cf40 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/SelftestProvider.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/SelftestProvider.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ */ package org.newsclub.net.unix.rmi; +import java.io.PrintWriter; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -25,7 +26,7 @@ /** * Provides references to all "junixsocket-rmi" tests that should be included in * junixsocket-selftest. - * + * * @author Christian Kohlschütter */ public class SelftestProvider { @@ -45,4 +46,7 @@ public Map[]> tests() { public Set modulesDisabledByDefault() { return Collections.emptySet(); } + + public void printAdditionalProperties(PrintWriter out) { + } } diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/ShutdownHookTestBase.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/ShutdownHookTestBase.java index a8a29993d..c33fb9771 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/ShutdownHookTestBase.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/ShutdownHookTestBase.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/TestBase.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/TestBase.java index 367e2f9d3..4dd405ee3 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/TestBase.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/TestBase.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,11 @@ protected AFNaming newNamingTestInstance() throws IOException { return AFUNIXNaming.newPrivateInstance(); } + @SuppressWarnings("all") + @Deprecated + protected final void finalize() { + } + @BeforeEach public void setUp() throws IOException, AlreadyBoundException { naming = newNamingTestInstance(); diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/TestRegistryServer.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/TestRegistryServer.java index ca636f569..65c995839 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/TestRegistryServer.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/TestRegistryServer.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,14 +23,15 @@ import java.rmi.RemoteException; import com.kohlschutter.annotations.compiletime.SuppressFBWarnings; +import com.kohlschutter.testutil.TestStackTraceUtil; import com.kohlschutter.util.SystemPropertyUtil; /** * A simple RMI Registry that is launched as forked Java VM from unit tests such as * {@link RemoteRegistryTest}. - * + * * Important: The server will terminate ca. 10 seconds after starting. - * + * * @author Christian Kohlschütter */ public class TestRegistryServer { @@ -89,7 +90,7 @@ public void run() { try { naming.shutdownRegistry(); } catch (RemoteException e) { - e.printStackTrace(); + TestStackTraceUtil.printStackTrace(e); } System.exit(0); // NOPMD diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/TestService.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/TestService.java index 05fd71389..70b486e62 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/TestService.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/TestService.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ /** * A test service. - * + * * @author Christian Kohlschütter */ public interface TestService extends Remote { diff --git a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/TestServiceImpl.java b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/TestServiceImpl.java index c1d9d2092..dace95f33 100644 --- a/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/TestServiceImpl.java +++ b/junixsocket-rmi/src/test/java/org/newsclub/net/unix/rmi/TestServiceImpl.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2022 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,10 +37,10 @@ /** * The implementation for the test service. - * + * * @author Christian Kohlschütter */ -public class TestServiceImpl implements TestService, Closeable { +public final class TestServiceImpl implements TestService, Closeable { private final File tmpFile; private final AFUNIXRMISocketFactory socketFactory; private final RemoteCloseableThingImpl.NotCloseableImpl testNotCloseableImpl = diff --git a/junixsocket-rmi/src/test/java8/org/newsclub/net/unix/rmi/RemoteRegistryTest.java b/junixsocket-rmi/src/test/java8/org/newsclub/net/unix/rmi/RemoteRegistryTest.java index b99e8d00e..791001865 100644 --- a/junixsocket-rmi/src/test/java8/org/newsclub/net/unix/rmi/RemoteRegistryTest.java +++ b/junixsocket-rmi/src/test/java8/org/newsclub/net/unix/rmi/RemoteRegistryTest.java @@ -1,7 +1,7 @@ /* * junixsocket * - * Copyright 2009-2021 Christian Kohlschütter + * Copyright 2009-2024 Christian Kohlschütter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/junixsocket-selftest-android/README.md b/junixsocket-selftest-android/README.md new file mode 100644 index 000000000..2cbdb2f72 --- /dev/null +++ b/junixsocket-selftest-android/README.md @@ -0,0 +1,30 @@ + +![](SelftestApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png) + +# junixsocket Selftest app for Android + +This app runs the junixsocket-selftest code on Android. + +## Installation instructions + +Use Android Studio to build and run. + +## Screenshot + +![junixsocket Selftest results](screenshot.png) + +## License + +Copyright 2023 Christian Kohlschütter + +Licensed 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 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/junixsocket-selftest-android/SelftestApp/.gitignore b/junixsocket-selftest-android/SelftestApp/.gitignore new file mode 100644 index 000000000..aa724b770 --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/junixsocket-selftest-android/SelftestApp/app/.gitignore b/junixsocket-selftest-android/SelftestApp/app/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/junixsocket-selftest-android/SelftestApp/app/build.gradle b/junixsocket-selftest-android/SelftestApp/app/build.gradle new file mode 100644 index 000000000..144cbfabb --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/build.gradle @@ -0,0 +1,87 @@ +plugins { + id 'com.android.application' +} + +android { + namespace 'com.kohlschutter.junixsocket.selftest.android' + compileSdk 33 + ndkVersion '25.2.9519653' + + defaultConfig { + applicationId "com.kohlschutter.junixsocket.selftest.android" + minSdk 26 + targetSdk 32 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + buildFeatures { + viewBinding true + } + + packagingOptions { + // junit jars contain duplicate META-INF/LICENSE.md files + resources.excludes += "META-INF/LICENSE*" + + // exclude GraalVM config (not necessary on Android) + resources.excludes += "META-INF/native-image/**/*" + + // exclude other native image files erroneously included + resources.excludes += "lib/*/jni/*" + } +} + +dependencies { + + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.9.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + + implementation 'com.kohlschutter.junixsocket:junixsocket-native-android:2.10.1@aar' + + implementation 'com.kohlschutter.junixsocket:junixsocket-common:2.10.1' + + implementation 'com.kohlschutter.junixsocket:junixsocket-tipc:2.10.1' + implementation 'com.kohlschutter.junixsocket:junixsocket-vsock:2.10.1' + implementation 'com.kohlschutter.junixsocket:junixsocket-rmi:2.10.1' + implementation 'com.kohlschutter.junixsocket:junixsocket-ssl:2.10.1' + implementation 'com.kohlschutter.junixsocket:junixsocket-darwin:2.10.1' + + // junixsocket-ssl will automatically use the real Bouncycastle on Android when available. + // This is REQUIRED for junixsocket-ssl tests and RECOMMENDED for junixsocket-ssl users. + // Android's built-in Bouncycastle fork lacks PKCS12 support. + implementation 'org.bouncycastle:bcprov-jdk18on:1.76' + + // junixsocket-ssl will automatically use Bouncycastle-TLS on Android when available. + // This is REQUIRED for junixsocket-ssl tests and RECOMMENDED for junixsocket-ssl users. + // Android's built-in TLS code has problems with sending SNI hostname requests. + implementation 'org.bouncycastle:bctls-jdk18on:1.76' + + // selftest-specifics + implementation('com.kohlschutter.junixsocket:junixsocket-selftest:2.10.1') { + exclude group: "com.kohlschutter.junixsocket", module: "junixsocket-core" + exclude group: "com.kohlschutter.junixsocket", module: "junixsocket-native-common" + exclude group: "com.kohlschutter.junixsocket", module: "junixsocket-native-custom" + } + implementation 'com.kohlschutter:kohlschutter-util:1.6.3' + implementation 'com.kohlschutter:kohlschutter-test-util:1.6.3' + implementation 'org.junit.platform:junit-platform-console:1.9.3' + implementation 'org.junit.jupiter:junit-jupiter-engine:5.9.3' + implementation 'org.junit.jupiter:junit-jupiter-api:5.9.3' + + // testImplementation 'junit:junit:4.13.2' + //androidTestImplementation 'androidx.test.ext:junit:1.1.4' + //androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' +} diff --git a/junixsocket-selftest-android/SelftestApp/app/proguard-rules.pro b/junixsocket-selftest-android/SelftestApp/app/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/AndroidManifest.xml b/junixsocket-selftest-android/SelftestApp/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..27ab08364 --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/ic_launcher-playstore.png b/junixsocket-selftest-android/SelftestApp/app/src/main/ic_launcher-playstore.png new file mode 100644 index 000000000..63affd6d3 Binary files /dev/null and b/junixsocket-selftest-android/SelftestApp/app/src/main/ic_launcher-playstore.png differ diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/java/com/kohlschutter/junixsocket/selftest/android/ScrollingActivity.java b/junixsocket-selftest-android/SelftestApp/app/src/main/java/com/kohlschutter/junixsocket/selftest/android/ScrollingActivity.java new file mode 100644 index 000000000..28540927b --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/java/com/kohlschutter/junixsocket/selftest/android/ScrollingActivity.java @@ -0,0 +1,126 @@ +package com.kohlschutter.junixsocket.selftest.android; + +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.core.widget.NestedScrollView; + +import com.google.android.material.appbar.CollapsingToolbarLayout; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; +import com.kohlschutter.junixsocket.selftest.android.databinding.ActivityScrollingBinding; + +import org.newsclub.net.unix.AFSocket; +import org.newsclub.net.unix.selftest.Selftest; + +import java.io.IOException; +import java.io.Writer; +import java.util.concurrent.CompletableFuture; + +public class ScrollingActivity extends AppCompatActivity { + + private ActivityScrollingBinding binding; + + private static final class TextViewWriter extends Writer { + + private final NestedScrollView sv; + private final TextView tv; + + TextViewWriter(NestedScrollView sv, TextView tv, boolean clear) { + this.sv = sv; + this.tv = tv; + sv.setOverScrollMode(NestedScrollView.OVER_SCROLL_ALWAYS); + + if (clear) { + tv.post(() -> tv.setText("")); + flush(); + } + } + + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + String s = new String(cbuf, off, len); + tv.post(() -> tv.append(s)); + + // Also write to log + System.err.print(s); + } + + @Override + public void flush() { + sv.post(() -> { + sv.fullScroll(View.FOCUS_DOWN); + }); + } + + @Override + public void close() { + flush(); + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + System.out.println("junixsocket supported: "+AFSocket.isSupported()); + binding = ActivityScrollingBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + Toolbar toolbar = binding.toolbar; + setSupportActionBar(toolbar); + CollapsingToolbarLayout toolBarLayout = binding.toolbarLayout; + toolBarLayout.setTitle(getTitle()); + binding.selftestText.selftestScrollView.fullScroll(View.FOCUS_DOWN); + + NestedScrollView scrollView = binding.selftestText.selftestScrollView; + TextView textView = binding.selftestText.selftestText; + + try { + CompletableFuture.runAsync(() -> { + try { + Selftest.runSelftest(new TextViewWriter(scrollView, textView, true)); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + + FloatingActionButton fab = binding.fab; + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Snackbar.make(view, "Not yet implemented", Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + } + }); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_scrolling, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + return true; + } + return super.onOptionsItemSelected(item); + } +} \ No newline at end of file diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/junixsocket-selftest-android/SelftestApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 000000000..2b068d114 --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/drawable/ic_launcher_background.xml b/junixsocket-selftest-android/SelftestApp/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..07d5da9cb --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/layout-w1240dp/content_scrolling.xml b/junixsocket-selftest-android/SelftestApp/app/src/main/res/layout-w1240dp/content_scrolling.xml new file mode 100644 index 000000000..98efc6102 --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/res/layout-w1240dp/content_scrolling.xml @@ -0,0 +1,23 @@ + + + + diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/layout-w936dp/content_scrolling.xml b/junixsocket-selftest-android/SelftestApp/app/src/main/res/layout-w936dp/content_scrolling.xml new file mode 100644 index 000000000..35f2b3967 --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/res/layout-w936dp/content_scrolling.xml @@ -0,0 +1,21 @@ + + + + diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/layout/activity_scrolling.xml b/junixsocket-selftest-android/SelftestApp/app/src/main/res/layout/activity_scrolling.xml new file mode 100644 index 000000000..194301cdc --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/res/layout/activity_scrolling.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/layout/content_scrolling.xml b/junixsocket-selftest-android/SelftestApp/app/src/main/res/layout/content_scrolling.xml new file mode 100644 index 000000000..1b54760f0 --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/res/layout/content_scrolling.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/menu/menu_scrolling.xml b/junixsocket-selftest-android/SelftestApp/app/src/main/res/menu/menu_scrolling.xml new file mode 100644 index 000000000..36cabcab9 --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/res/menu/menu_scrolling.xml @@ -0,0 +1,10 @@ +

+ + \ No newline at end of file diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..036d09bc5 --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..036d09bc5 --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-hdpi/ic_launcher.png b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..aa63b16a1 Binary files /dev/null and b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..e6e2a6e7a Binary files /dev/null and b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 000000000..21b772f67 Binary files /dev/null and b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-mdpi/ic_launcher.png b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..1590b239e Binary files /dev/null and b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..087ddce0a Binary files /dev/null and b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 000000000..cfcc92a40 Binary files /dev/null and b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..72d31a1bd Binary files /dev/null and b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..9f0a0893c Binary files /dev/null and b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 000000000..1b33c67ed Binary files /dev/null and b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..f6762b457 Binary files /dev/null and b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..d256c1b1b Binary files /dev/null and b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..492db1289 Binary files /dev/null and b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..467c68cd8 Binary files /dev/null and b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..cc4cae5ab Binary files /dev/null and b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..bbb135078 Binary files /dev/null and b/junixsocket-selftest-android/SelftestApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/values-land/dimens.xml b/junixsocket-selftest-android/SelftestApp/app/src/main/res/values-land/dimens.xml new file mode 100644 index 000000000..96d25a4e7 --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/res/values-land/dimens.xml @@ -0,0 +1,4 @@ + + 48dp + 48dp + \ No newline at end of file diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/values-night/themes.xml b/junixsocket-selftest-android/SelftestApp/app/src/main/res/values-night/themes.xml new file mode 100644 index 000000000..54202f550 --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/values-w1240dp/dimens.xml b/junixsocket-selftest-android/SelftestApp/app/src/main/res/values-w1240dp/dimens.xml new file mode 100644 index 000000000..d73f4a359 --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/res/values-w1240dp/dimens.xml @@ -0,0 +1,3 @@ + + 200dp + \ No newline at end of file diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/values-w600dp/dimens.xml b/junixsocket-selftest-android/SelftestApp/app/src/main/res/values-w600dp/dimens.xml new file mode 100644 index 000000000..96d25a4e7 --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/res/values-w600dp/dimens.xml @@ -0,0 +1,4 @@ + + 48dp + 48dp + \ No newline at end of file diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/values/colors.xml b/junixsocket-selftest-android/SelftestApp/app/src/main/res/values/colors.xml new file mode 100644 index 000000000..f8c6127d3 --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/values/dimens.xml b/junixsocket-selftest-android/SelftestApp/app/src/main/res/values/dimens.xml new file mode 100644 index 000000000..89ef4658d --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + 180dp + 16dp + 16dp + \ No newline at end of file diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/values/ic_launcher_background.xml b/junixsocket-selftest-android/SelftestApp/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 000000000..a76c72f5e --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #3DDCDC + \ No newline at end of file diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/values/strings.xml b/junixsocket-selftest-android/SelftestApp/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..6e20f6eab --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/res/values/strings.xml @@ -0,0 +1,93 @@ + + My Application + + "Material is the metaphor.\n\n" + + "A material metaphor is the unifying theory of a rationalized space and a system of motion." + "The material is grounded in tactile reality, inspired by the study of paper and ink, yet " + "technologically advanced and open to imagination and magic.\n" + "Surfaces and edges of the material provide visual cues that are grounded in reality. The " + "use of familiar tactile attributes helps users quickly understand affordances. Yet the " + "flexibility of the material creates new affordances that supercede those in the physical " + "world, without breaking the rules of physics.\n" + "The fundamentals of light, surface, and movement are key to conveying how objects move, " + "interact, and exist in space and in relation to each other. Realistic lighting shows " + "seams, divides space, and indicates moving parts.\n\n" + + "Bold, graphic, intentional.\n\n" + + "The foundational elements of print based design typography, grids, space, scale, color, " + "and use of imagery guide visual treatments. These elements do far more than please the " + "eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge " + "imagery, large scale typography, and intentional white space create a bold and graphic " + "interface that immerse the user in the experience.\n" + "An emphasis on user actions makes core functionality immediately apparent and provides " + "waypoints for the user.\n\n" + + "Motion provides meaning.\n\n" + + "Motion respects and reinforces the user as the prime mover. Primary user actions are " + "inflection points that initiate motion, transforming the whole design.\n" + "All action takes place in a single environment. Objects are presented to the user without " + "breaking the continuity of experience even as they transform and reorganize.\n" + "Motion is meaningful and appropriate, serving to focus attention and maintain continuity. " + "Feedback is subtle yet clear. Transitions are efficient yet coherent.\n\n" + + "3D world.\n\n" + + "The material environment is a 3D space, which means all objects have x, y, and z " + "dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the " + "positive z-axis extending towards the viewer. Every sheet of material occupies a single " + "position along the z-axis and has a standard 1dp thickness.\n" + "On the web, the z-axis is used for layering and not for perspective. The 3D world is " + "emulated by manipulating the y-axis.\n\n" + + "Light and shadow.\n\n" + + "Within the material environment, virtual lights illuminate the scene. Key lights create " + "directional shadows, while ambient light creates soft shadows from all angles.\n" + "Shadows in the material environment are cast by these two light sources. In Android " + "development, shadows occur when light sources are blocked by sheets of material at " + "various positions along the z-axis. On the web, shadows are depicted by manipulating the " + "y-axis only. The following example shows the card with a height of 6dp.\n\n" + + "Resting elevation.\n\n" + + "All material objects, regardless of size, have a resting elevation, or default elevation " + "that does not change. If an object changes elevation, it should return to its resting " + "elevation as soon as possible.\n\n" + + "Component elevations.\n\n" + + "The resting elevation for a component type is consistent across apps (e.g., FAB elevation " + "does not vary from 6dp in one app to 16dp in another app).\n" + "Components may have different resting elevations across platforms, depending on the depth " + "of the environment (e.g., TV has a greater depth than mobile or desktop).\n\n" + + "Responsive elevation and dynamic elevation offsets.\n\n" + + "Some component types have responsive elevation, meaning they change elevation in response " + "to user input (e.g., normal, focused, and pressed) or system events. These elevation " + "changes are consistently implemented using dynamic elevation offsets.\n" + "Dynamic elevation offsets are the goal elevation that a component moves towards, relative " + "to the component’s resting state. They ensure that elevation changes are consistent " + "across actions and component types. For example, all components that lift on press have " + "the same elevation change relative to their resting elevation.\n" + "Once the input event is completed or cancelled, the component will return to its resting " + "elevation.\n\n" + + "Avoiding elevation interference.\n\n" + + "Components with responsive elevations may encounter other components as they move between " + "their resting elevations and dynamic elevation offsets. Because material cannot pass " + "through other material, components avoid interfering with one another any number of ways, " + "whether on a per component basis or using the entire app layout.\n" + "On a component level, components can move or be removed before they cause interference. " + "For example, a floating action button (FAB) can disappear or move off screen before a " + "user picks up a card, or it can move if a snackbar appears.\n" + "On the layout level, design your app layout to minimize opportunities for interference. " + "For example, position the FAB to one side of stream of a cards so the FAB won’t interfere " + "when a user tries to pick up one of cards.\n\n" + + Settings + \ No newline at end of file diff --git a/junixsocket-selftest-android/SelftestApp/app/src/main/res/values/themes.xml b/junixsocket-selftest-android/SelftestApp/app/src/main/res/values/themes.xml new file mode 100644 index 000000000..b4611aa13 --- /dev/null +++ b/junixsocket-selftest-android/SelftestApp/app/src/main/res/values/themes.xml @@ -0,0 +1,25 @@ + + + + + + +