diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..657a13cc --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,89 @@ +name: Build and publish snapshots +on: + push: + branches: [ javabuild ] + +jobs: + build: + strategy: + matrix: + os: [ ubuntu-latest, macos-latest ] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + with: + ref: javabuild + - name: Install latest bash on macOS + if: runner.os == 'macOS' + run: | + brew update + brew install bash + brew install coreutils + - name: Set environment variables + run: | + echo "OS=${RUNNER_OS,,}" >> $GITHUB_ENV + echo "ARCH=${RUNNER_ARCH,,}" >> $GITHUB_ENV + if [[ ${{ runner.os }} == "Linux" ]] + then + platform=unix + classifier=linux-x86_64 + elif [[ ${{ runner.os }} == "macOS" ]] + then + platform=mac + if [[ ${{ runner.arch }} == "X64" ]] + then + classifier=darwin-x86_64 + else + classifier=darwin-aarch64 + fi + elif [[ ${{ runner.os }} == "Windows" ]] + then + platform=windows + classifier=win-x86_64 + fi + echo "PLATFORM=$platform" >> $GITHUB_ENV + echo "CLASSIFIER=$classifier" >> $GITHUB_ENV + shell: bash + - name: Download OpenJDK 19 + id: download + uses: oracle-actions/setup-java@v1 + with: + website: jdk.java.net + release: 19 + install: false + - name: Setup Java and Apache Maven + uses: actions/setup-java@v3 + with: + distribution: jdkfile + jdkFile: ${{ steps.download.outputs.archive }} + java-version: ${{ steps.download.outputs.version }} + server-id: gluon-nexus + server-username: MAVEN_USERNAME + server-password: MAVEN_CENTRAL_TOKEN + - name: Checkout tools repo + run: | + echo "print env variables" + echo ${{ env.OS }} + echo ${{ env.ARCH }} + echo ${{ env.PLATFORM }} + echo ${{ env.CLASSIFIER }} + cd $GITHUB_WORKSPACE + git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git + - name: Install rustup + run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + - name: Download and extract JEXTRACT + run: | + download_url="https://download.java.net/java/early_access/jextract/2/openjdk-19-jextract+2-3_${{ env.OS }}-${{ env.ARCH }}_bin.tar.gz" + wget -q --show-progress -O $RUNNER_TEMP/jextract.tar.gz $download_url + tar -xvzf $RUNNER_TEMP/jextract.tar.gz -C $GITHUB_WORKSPACE + - name: Build + run: | + export PATH=$GITHUB_WORKSPACE/depot_tools/:$PATH + make java PLATFORM=${{ env.PLATFORM }} JEXTRACT=$GITHUB_WORKSPACE/jextract-19 JDK=$JAVA_HOME TARGET_ARCH=${{ env.ARCH }} MACOS_SDK_VERSION=12.1 + - name: Deploy snapshot + run: | + cd src/java/tring + mvn -Dclassifier=${{ env.CLASSIFIER }} deploy + env: + MAVEN_USERNAME: ${{ secrets.GLUON_NEXUS_USERNAME }} + MAVEN_CENTRAL_TOKEN: ${{ secrets.GLUON_NEXUS_PASSWORD }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 271f8a73..3736b890 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,9 @@ src/jar.list .project .classpath org.eclipse.buildship.core.prefs +src/java/build +src/java/tring/src/gen-sources +src/java/tring/src/main/resources/libringrtc.dylib +src/java/tring/target +src/java/tringapi/target/ +src/rust/tringlib.h diff --git a/Makefile b/Makefile index 4f514df9..d489afa9 100644 --- a/Makefile +++ b/Makefile @@ -32,10 +32,11 @@ help: $(Q) echo " ios -- download WebRTC and build for the iOS platform" $(Q) echo " android -- download WebRTC and build for the Android platform" $(Q) echo " electron -- build an Electron library" + $(Q) echo " java -- build a Java library" $(Q) echo " cli -- build the test cli (1:1 calls)" $(Q) echo " gctc -- build the test cli (group calls)" $(Q) echo - $(Q) echo "For the electon/cli/gctc builds, you can specify an optional platform" + $(Q) echo "For the electron/java/cli/gctc builds, you can specify an optional platform" $(Q) echo "which will download WebRTC. For example:" $(Q) echo " $ make electron PLATFORM=unix" $(Q) echo @@ -86,6 +87,14 @@ electron: fi $(Q) (cd src/node && yarn install && yarn build) +java: + $(Q) if [ "$(PLATFORM)" != "" ] ; then \ + echo "java: Preparing workspace for $(PLATFORM)" ; \ + ./bin/prepare-workspace $(PLATFORM) ; \ + fi + echo "java: Release build" ; \ + ./bin/build-java -r + cli: $(Q) if [ "$(PLATFORM)" != "" ] ; then \ echo "cli: Preparing workspace for $(PLATFORM)" ; \ @@ -119,6 +128,7 @@ clean: $(Q) ./bin/build-gctc --clean $(Q) ./bin/build-electron --clean $(Q) ./bin/build-ios --clean + $(Q) ./bin/build-java --clean $(Q) rm -rf ./src/webrtc/src/out PHONY += distclean diff --git a/bin/build-java b/bin/build-java new file mode 100755 index 00000000..989f40a9 --- /dev/null +++ b/bin/build-java @@ -0,0 +1,163 @@ +#!/bin/sh + +# +# Copyright 2019-2021 Signal Messenger, LLC +# SPDX-License-Identifier: AGPL-3.0-only +# + +set -e + +# shellcheck source=bin/env.sh +. "$(dirname "$0")"/env.sh + +JEXTRACT=${JEXTRACT:-/opt/jextract-19} +JDK=${JDK:-/opt/jdk-19} +TARGET_ARCH=${TARGET_ARCH:-x64} + +# darwin only +DEFAULT_MACOS_SDK_VERSION="12.3" +MACOS_SDK_VERSION=${MACOS_SDK_VERSION:-$DEFAULT_MACOS_SDK_VERSION} +MACOS_SDK_PATH="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX${MACOS_SDK_VERSION}.sdk" + +usage() +{ + echo 'usage: build-java [-d|-r|-c] + where: + -r to create a release build + -d to create a debug build (which may fail to build!) + -c to clean the build artifacts' +} + +clean() +{ + # Remove all possible artifact directories. + rm -rf ./src/rust/target/debug + rm -rf ./src/rust/target/release + rm -rf ./src/webrtc/src/out/debug + rm -rf ./src/webrtc/src/out/release + mvn -f ./src/java/tring clean +} + +BUILD_TYPE=release + +while [ "$1" != "" ]; do + case $1 in + -d | --debug ) + BUILD_TYPE=debug + ;; + -r | --release ) + BUILD_TYPE=release + ;; + -c | --clean ) + clean + exit + ;; + -h | --help ) + usage + exit + ;; + * ) + usage + exit 1 + esac + shift +done + +case "$TARGET_ARCH" in + "x64") + GN_ARCH=x64 + CARGO_ARCH=x86_64 + ;; + "ia32") + GN_ARCH=x86 + CARGO_ARCH=i686 + ;; + "arm64") + GN_ARCH=arm64 + CARGO_ARCH=aarch64 + ;; + *) + echo "Unsupported architecture" + exit 1 + ;; +esac + +hash rustup 2>/dev/null || { echo >&2 "Make sure you have rustup installed and properly configured! Aborting."; exit 1; } + +RUSTFLAGS_WIN= + +case "$(rustup show active-toolchain)" in + *"x86_64-apple-darwin"* | *"aarch64-apple-darwin"* ) + DEFAULT_PLATFORM="darwin" + CARGO_TARGET="${CARGO_ARCH}-apple-darwin" + ;; + *"x86_64-pc-windows"* ) + DEFAULT_PLATFORM="win32" + CARGO_TARGET="${CARGO_ARCH}-pc-windows-msvc" + # Static linking to prevent build errors on Windows ia32 + RUSTFLAGS_WIN="-C target-feature=+crt-static" + ;; + *"x86_64-unknown-linux"* ) + DEFAULT_PLATFORM="linux" + CARGO_TARGET="${CARGO_ARCH}-unknown-linux-gnu" + ;; + * ) + printf "Unknown platform detected!\nPlease make sure you have installed a valid Rust toolchain via rustup! Aborting.\n" + exit 1 +esac + +echo "Building for platform ${DEFAULT_PLATFORM}, TARGET_ARCH=${TARGET_ARCH}, GN_ARCH=${GN_ARCH}, CARGO_TARGET=${CARGO_TARGET}", OUTPUT_DIR=${OUTPUT_DIR} + +export MACOSX_DEPLOYMENT_TARGET="10.10" + +# Build WebRTC. +( + cd src/webrtc/src + WEBRTC_ARGS="target_cpu=\"${GN_ARCH}\" rtc_build_examples=false rtc_build_tools=false rtc_include_tests=false rtc_enable_protobuf=false rtc_use_x11=false rtc_enable_sctp=false rtc_libvpx_build_vp9=true rtc_include_ilbc=false" + + if [ "${BUILD_TYPE}" = "debug" ] + then + gn gen -C "${OUTPUT_DIR}"/debug "--args=${WEBRTC_ARGS}" + ninja -C "${OUTPUT_DIR}"/debug + else + gn gen -C "${OUTPUT_DIR}"/release "--args=${WEBRTC_ARGS} is_debug=false" + ninja -C "${OUTPUT_DIR}"/release + fi +) + +# Build and link the final RingRTC library. +( + cd src/rust + + if [ "${BUILD_TYPE}" = "debug" ] + then + RUSTFLAGS="${RUSTFLAGS_WIN}" OUTPUT_DIR="${OUTPUT_DIR}" cargo build --target ${CARGO_TARGET} --features java + else + OUTPUT_DIR="${OUTPUT_DIR}" cargo build --target ${CARGO_TARGET} --lib --features java --release + fi + + mkdir -p ../java/tring/src/main/resources + if [ $DEFAULT_PLATFORM = "darwin" ] + then + mkdir -p ../java/build/darwin + cp -f target/${CARGO_TARGET}/${BUILD_TYPE}/libringrtc.dylib ../java/tring/src/main/resources/libringrtc.dylib + # cp -f target/${CARGO_TARGET}/${BUILD_TYPE}/libringrtc.dylib ../java/build/darwin/libringrtc-"${TARGET_ARCH}" + ${JEXTRACT}/bin/jextract -I${JDK}include -I${JDK}/include/darwin -I${MACOS_SDK_PATH}/usr/include --output ../java/tring/src/gen-sources/java --source -t io.privacyresearch.tring tringlib.h + elif [ $DEFAULT_PLATFORM = "win32" ] + then + mkdir -p ../java/build/win32 + cp -f target/${CARGO_TARGET}/${BUILD_TYPE}/ringrtc.dll ../java/build/win32/libringrtc-"${TARGET_ARCH}".java + ${JEXTRACT}/bin/jextract -I${JDK}include -I${JDK}/include/win32 --output ../java/tring/src/main/java --source -t io.privacyresearch.tring tringlib.h + elif [ $DEFAULT_PLATFORM = "linux" ] + then + mkdir -p ../java/build/linux + cp -f target/${CARGO_TARGET}/${BUILD_TYPE}/libringrtc.so ../java/tring/src/main/resources/libringrtc.so + #cp -f target/${CARGO_TARGET}/${BUILD_TYPE}/libringrtc.so ../java/build/linux/libringrtc-"${TARGET_ARCH}".java + ${JEXTRACT}/bin/jextract -I${JDK}include -I${JDK}/include/linux --output ../java/tring/src/main/java --source -t io.privacyresearch.tring tringlib.h + fi + cd ../java/tringapi + mvn clean install + cd ../tring + echo "mvn -Dclassifier=$DEFAULT_PLATFORM-$CARGO_ARCH clean install" + mvn -Dclassifier=$DEFAULT_PLATFORM-$CARGO_ARCH clean install +) diff --git a/src/java/README.md b/src/java/README.md new file mode 100644 index 00000000..18ee0c7e --- /dev/null +++ b/src/java/README.md @@ -0,0 +1,14 @@ +The Java implementation consists of 2 Java projects, and a Rust component. + +tringapi: this contains the API that can be accessed by the application, +without any dependencies (no application model classes or ringrtc structs). +It uses the Java ServiceLoader concept to find implementations of the +TringService. + +tring: this contains platform-specific implementations of the TringService. +The source code is mainly auto-generated by jextract. The implementation +itself is in TringServiceImpl, and it is made available via module-info.java + +Rust component: +java.rs contains the implementation of methods invoked by TringServiceImpl. +It is similar to electron.js diff --git a/src/java/tring/pom.xml b/src/java/tring/pom.xml new file mode 100644 index 00000000..9de8632d --- /dev/null +++ b/src/java/tring/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + io.privacyresearch + tring + 0.0.12-SNAPSHOT + jar + + UTF-8 + 19 + 19 + + + + io.privacyresearch + tringapi + 0.0.9-SNAPSHOT + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + --enable-preview + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.3.0 + + + generate-sources + + add-source + + + + src/gen-sources + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + + false + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.2 + + + classifier-build + + jar + + + ${classifier} + + + + + + + + + gluon-nexus + https://nexus.gluonhq.com/nexus/content/repositories/releases + + + gluon-nexus + https://nexus.gluonhq.com/nexus/content/repositories/public-snapshots + + + + diff --git a/src/java/tring/src/main/java/io/privacyresearch/tring/NativeLibLoader.java b/src/java/tring/src/main/java/io/privacyresearch/tring/NativeLibLoader.java new file mode 100644 index 00000000..9c81b3e1 --- /dev/null +++ b/src/java/tring/src/main/java/io/privacyresearch/tring/NativeLibLoader.java @@ -0,0 +1,20 @@ +package io.privacyresearch.tring; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +public class NativeLibLoader { + + public static String loadLibrary() throws IOException { + String libName = "/"+System.mapLibraryName("ringrtc"); + System.err.println("Will try to load "+libName); + InputStream is = NativeLibLoader.class.getResourceAsStream(libName); + Path target = Files.createTempFile("", ""); + Files.copy(is, target, StandardCopyOption.REPLACE_EXISTING); + System.load(target.toString()); + return libName; + } +} diff --git a/src/java/tring/src/main/java/io/privacyresearch/tring/OldTringApi.java b/src/java/tring/src/main/java/io/privacyresearch/tring/OldTringApi.java new file mode 100644 index 00000000..2d8cc37b --- /dev/null +++ b/src/java/tring/src/main/java/io/privacyresearch/tring/OldTringApi.java @@ -0,0 +1,18 @@ +package io.privacyresearch.tring; + +import java.util.List; + +/** + * + * @author johan + */ +@Deprecated +public interface OldTringApi { + + void statusCallback(long callId, long peerId, int dir, int type); + + void answerCallback(byte[] opaque); + + void iceUpdateCallback(List iceCandidates); + +} diff --git a/src/java/tring/src/main/java/io/privacyresearch/tring/OldTringBridge.java b/src/java/tring/src/main/java/io/privacyresearch/tring/OldTringBridge.java new file mode 100644 index 00000000..7cbc3c98 --- /dev/null +++ b/src/java/tring/src/main/java/io/privacyresearch/tring/OldTringBridge.java @@ -0,0 +1,44 @@ +package io.privacyresearch.tring; + +//import io.privacyresearch.tringapi.TringApi; +//import io.privacyresearch.tringapi.TringService; +// +//import java.util.List; +//import java.util.Optional; +//import java.util.ServiceLoader; + +/** + * + * @author johan + */ +@Deprecated +public class OldTringBridge { + // ready to be removed in next commit +// +// private TringService service; +// +// public OldTringBridge(TringApi api) { +// ServiceLoader loader = ServiceLoader.load(TringService.class); +// Optional serviceOpt = loader.findFirst(); +// this.service = serviceOpt.get(); +// this.service.setApi(api); +// } +// +// public void acceptCall() { +// service.acceptCall(); +// } +// +// public void proceed(long callId) { +// service.proceed(callId); +// } +// +// public void receivedIce(long callId, int senderDeviceId, List ice) { +// receivedIce(callId, senderDeviceId, ice); +// } +// +// public void receivedOffer(String peerId, long callId, int senderDeviceId, int receiverDeviceId, +// byte[] senderKey, byte[] receiverKey, byte[] opaque) { +// receivedOffer(peerId, callId, senderDeviceId, receiverDeviceId, senderKey, receiverKey, opaque); +// } + +} diff --git a/src/java/tring/src/main/java/io/privacyresearch/tring/TringServiceImpl.java b/src/java/tring/src/main/java/io/privacyresearch/tring/TringServiceImpl.java new file mode 100644 index 00000000..22b92687 --- /dev/null +++ b/src/java/tring/src/main/java/io/privacyresearch/tring/TringServiceImpl.java @@ -0,0 +1,459 @@ +package io.privacyresearch.tring; + +import io.privacyresearch.tringapi.TringFrame; +import io.privacyresearch.tringapi.TringService; +import java.lang.foreign.Addressable; +import java.lang.foreign.MemoryAddress; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.MemorySession; +import java.lang.foreign.ValueLayout; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class TringServiceImpl implements TringService { + + static final int BANDWIDTH_QUALITY_HIGH = 2; + private static final TringService instance = new TringServiceImpl(); + private static boolean nativeSupport = false; + private static long nativeVersion = 0; + + private MemorySession scope; + private long callEndpoint; + private io.privacyresearch.tringapi.TringApi api; + private long activeCallId; + static String libName = "unknown"; + BlockingQueue frameQueue = new LinkedBlockingQueue(); + + private static final Logger LOG = Logger.getLogger(TringServiceImpl.class.getName()); + + static { + try { + libName = NativeLibLoader.loadLibrary(); + nativeSupport = true; + nativeVersion = tringlib_h.getVersion(); + + } catch (Throwable ex) { + System.err.println("No native RingRTC support: "); + ex.printStackTrace(); + } + } + + + public TringServiceImpl() { + // no-op + } + + public static TringService provider() { + return instance; + } + + public String getVersionInfo() { + return "TringServiceImpl using "+libName; + } + + public static long getNativeVersion() { + return nativeVersion; + } + + @Override + public void setApi(io.privacyresearch.tringapi.TringApi api) { + this.api = api; + initiate(); + } + + private void initiate() { + scope = MemorySession.openShared(); + tringlib_h.initRingRTC(toJString(scope, "Hello from Java")); + this.callEndpoint = tringlib_h.createCallEndpoint(createStatusCallback(), + createAnswerCallback(), createOfferCallback(), + createIceUpdateCallback(), + createGenericCallback(), + createVideoFrameCallback()); + + } + + private void processAudioInputs() { + LOG.warning("Process Audio Inputs asked, not supported!"); + MemorySegment audioInputs = tringlib_h.getAudioInputs(scope, callEndpoint,0); + MemorySegment name = TringDevice.name$slice(audioInputs); + int namelen = (int)RString.len$get(name); + MemoryAddress namebuff = RString.buff$get(name); + MemorySegment ofAddress = MemorySegment.ofAddress(namebuff, namelen, scope); + ByteBuffer bb = ofAddress.asByteBuffer(); + byte[] bname = new byte[namelen]; + bb.get(bname, 0, (int)namelen); + String myname = new String(bname); + } + + @Override + public void receivedOffer(String peerId, long callId, int senderDeviceId, int receiverDeviceId, + byte[] senderKey, byte[] receiverKey, byte[] opaque) { + int mediaType = 0; + long ageSec = 0; + this.activeCallId = callId; + LOG.info("Pass received offer to tringlib"); + tringlib_h.receivedOffer(callEndpoint, toJString(scope, peerId), callId, mediaType, senderDeviceId, + receiverDeviceId, toJByteArray(scope, senderKey), toJByteArray(scope, receiverKey), + toJByteArray(scope, opaque), + ageSec); + } + + @Override + public void receivedOpaqueMessage(byte[] senderUuid, int senderDeviceId, + int localDeviceId, byte[] opaque, long age) { + tringlib_h.receivedOpaqueMessage(callEndpoint, + toJByteArray(scope, senderUuid), + senderDeviceId, + localDeviceId, + toJByteArray(scope, opaque), + age); + } + + @Override + public void receivedAnswer(String peerId, long callId, int senderDeviceId, + byte[] senderKey, byte[] receiverKey, byte[] opaque) { + int mediaType = 0; + long ageSec = 0; + this.activeCallId = callId; + LOG.info("Pass received answer to tringlib"); + tringlib_h.receivedAnswer(callEndpoint, toJString(scope, peerId), callId, senderDeviceId, + toJByteArray(scope, senderKey), toJByteArray(scope, receiverKey), + toJByteArray(scope, opaque)); + } + + public void setSelfUuid(String uuid) { + tringlib_h.setSelfUuid(callEndpoint, toJString(scope, uuid)); + } + + @Override + public void proceed(long callId, String iceUser, String icePwd, List ice) { + MemorySegment icePack = toJByteArray2D(scope, ice); + tringlib_h.setOutgoingAudioEnabled(callEndpoint, true); + tringlib_h.proceedCall(callEndpoint, callId, BANDWIDTH_QUALITY_HIGH, 0, + toJString(scope, iceUser), toJString(scope, icePwd), icePack); + } + + @Override + public void receivedIce(long callId, int senderDeviceId, List ice) { + MemorySegment icePack = toJByteArray2D(scope, ice); + tringlib_h.receivedIce(callEndpoint, callId, senderDeviceId, icePack); + } + + @Override + public void acceptCall() { + LOG.info("Set audioInput to 0"); + tringlib_h.setAudioInput(callEndpoint, (short)0); + LOG.info("Set audiorecording"); + tringlib_h.setOutgoingAudioEnabled(callEndpoint, true); + LOG.info("And now accept the call"); + tringlib_h.acceptCall(callEndpoint, activeCallId); + LOG.info("Accepted the call"); + } + + @Override + public void ignoreCall() { + LOG.info("Ignore the call"); + tringlib_h.ignoreCall(callEndpoint, activeCallId); + } + + @Override + public void hangupCall() { + LOG.info("Hangup the call"); + tringlib_h.hangupCall(callEndpoint); + } + + /** + * start a call and return the call_id + * @return the same call_id as the one we were passed, if success + */ + @Override + public long startOutgoingCall(long callId, String peerId, int localDeviceId, boolean enableVideo) { + LOG.info("Tring will start outgoing call to "+peerId+" with localDevice "+localDeviceId+" and enableVideo = "+enableVideo); + tringlib_h.setAudioInput(callEndpoint, (short)0); + tringlib_h.setAudioOutput(callEndpoint, (short)0); + tringlib_h.createOutgoingCall(callEndpoint, toJString(scope, peerId), enableVideo, localDeviceId, callId); + return callId; + } + + @Override + public void peekGroupCall(byte[] membershipProof) { + tringlib_h.peekGroupCall(callEndpoint, toJByteArray(scope, membershipProof)); + } + + // for testing only + public void setArray() { + LOG.info("SET ARRAY"); + int CAP = 1000000; + for (int i = 0; i < 1000; i++) { + try (MemorySession rscope = MemorySession.openConfined()) { + MemorySegment segment = MemorySegment.allocateNative(CAP, scope); + tringlib_h.fillLargeArray(123, segment); + ByteBuffer bb = segment.asByteBuffer(); + byte[] bar = new byte[CAP]; + bb.get(bar, 0, CAP); + LOG.info("Got Array " + i + " sized " + bar.length); + } + } + LOG.info("DONE"); + } + + @Override + public TringFrame getRemoteVideoFrame(boolean skip) { + int CAP = 5000000; + try (MemorySession rscope = MemorySession.openShared()) { + MemorySegment segment = MemorySegment.allocateNative(CAP, rscope); + long res = tringlib_h.fillRemoteVideoFrame(callEndpoint, segment, CAP); + if (res != 0) { + int w = (int) (res >> 16); + int h = (int) (res % (1 <<16)); + byte[] raw = new byte[w * h * 4]; + ByteBuffer bb = segment.asByteBuffer(); + bb.get(raw); + TringFrame answer = new TringFrame(w, h, -1, raw); + return answer; + } + } catch (Throwable t) { + t.printStackTrace(); + } + return null; + } + + @Override + public void enableOutgoingVideo(boolean enable) { + tringlib_h.setOutgoingVideoEnabled(callEndpoint, enable); + } + + @Override + public void sendVideoFrame(int w, int h, int pixelFormat, byte[] raw) { + try ( MemorySession session = MemorySession.openConfined()) { + MemorySegment buff = session.allocateArray(ValueLayout.JAVA_BYTE, raw); + tringlib_h.sendVideoFrame(callEndpoint, w, h, pixelFormat, buff); + } + } + + static MemorySegment toJByteArray2D(MemorySession ms, List rows) { + MemorySegment answer = JByteArray2D.allocate(ms); + JByteArray2D.len$set(answer, rows.size()); + MemorySegment rowsSegment = JByteArray2D.buff$slice(answer); + for (int i = 0; i < rows.size(); i++) { + MemorySegment singleRowSegment = toJByteArray(ms, rows.get(i)); + MemorySegment row = rowsSegment.asSlice(16 * i, 16); + row.copyFrom(singleRowSegment); + } + return answer; + } + + static MemorySegment toJByteArray(MemorySession ms, byte[] bytes) { + MemorySegment answer = JByteArray.allocate(ms); + JByteArray.len$set(answer, bytes.length); + MemorySegment byteBuffer = MemorySegment.allocateNative(bytes.length, ms); + MemorySegment.copy(bytes, 0, byteBuffer, ValueLayout.JAVA_BYTE, 0, bytes.length); + JByteArray.buff$set(answer, byteBuffer.address()); + return answer; + } + + static byte[] fromJArrayByte(MemorySession ms, MemorySegment jArrayByte) { + int len = (int)JArrayByte.len$get(jArrayByte); + MemorySegment dataSegment = JArrayByte.data$slice(jArrayByte).asSlice(0, len); + byte[] destArr = new byte[len]; + MemorySegment dstSeq = MemorySegment.ofArray(destArr); + dstSeq.copyFrom(dataSegment); + return destArr; + } + + static byte[] fromJByteArray(MemorySession ms, MemorySegment jByteArray) { + long len = JByteArray.len$get(jByteArray); + System.err.println("Need to read byte array with "+len+" bytes"); + for (int j = 0; j < 16; j++) { + byte b = jByteArray.get(ValueLayout.JAVA_BYTE, j); + System.err.println("b["+j+"] = "+b); + } + //VarHandle buffHandle = JByteArray.$struct$LAYOUT.varHandle(long.class, MemoryLayout.PathElement.groupElement("buff")); + + MemoryAddress pointer = JByteArray.buff$get(jByteArray); + System.err.println("pointer at "+ pointer.address()); +MemorySegment segment = MemorySegment.ofAddress(pointer, len, ms); +byte[] destArr = new byte[(int)len]; + MemorySegment dstSeq = MemorySegment.ofArray(destArr); + dstSeq.copyFrom(segment); + System.err.println("After copy, destArr = "+java.util.Arrays.toString(destArr)); + + + + for (int j = 0; j < len; j++) { + byte b = segment.get(ValueLayout.JAVA_BYTE, j); + System.err.println("Bb[" + j + "] = " + b); + + } + + + // MemoryAddress pointer = ptr.get(ValueLayout.ADDRESS, 0); + System.err.println("ptr = "+pointer+", val = " + pointer.toRawLongValue()); + System.err.println("ptr address = "+pointer.address()); + byte[] data = new byte[(int)len]; + for (int i =0; i < data.length; i++) { + data[i] = pointer.get(ValueLayout.JAVA_BYTE, i); + } + System.err.println("got data: "+java.util.Arrays.toString(data)); + byte p0 = pointer.address().get(ValueLayout.JAVA_BYTE, 0); + byte p1 = pointer.address().get(ValueLayout.JAVA_BYTE, 1); + byte p8 = pointer.address().get(ValueLayout.JAVA_BYTE, 8); + System.err.println("p0 = "+p0+", p1 = "+p1+", p8 = "+p8); +// MemorySegment byteSegment = JByteArray.ofAddress(pointer, ms); +// byte[] data = byteSegment.toArray(ValueLayout.JAVA_BYTE); + return data; + } + + static MemorySegment toJString(MemorySession ms, String src) { + MemorySegment answer = JString.allocate(ms); + byte[] bytes = src.getBytes(); + JString.len$set(answer, bytes.length); + MemorySegment byteBuffer = MemorySegment.allocateNative(bytes.length, ms); + MemorySegment.copy(bytes, 0, byteBuffer, ValueLayout.JAVA_BYTE, 0, bytes.length); + JString.buff$set(answer, byteBuffer.address()); + return answer; + } + + Addressable createStatusCallback() { + StatusCallbackImpl sci = new StatusCallbackImpl(); + MemorySegment seg = createCallEndpoint$statusCallback.allocate(sci, scope); + return seg.address(); + } + + + class StatusCallbackImpl implements createCallEndpoint$statusCallback { + @Override + public void apply(long id, long _x1, int direction, int type) { + LOG.info("Got new status from ringrtc, id = " + id+", x1 = " + _x1+", dir = " + direction+", type = "+type); + api.statusCallback(id, _x1, direction, type); + sendAck(); + } + } + + Addressable createAnswerCallback() { + AnswerCallbackImpl sci = new AnswerCallbackImpl(); + MemorySegment seg = createCallEndpoint$answerCallback.allocate(sci, scope); + return seg.address(); + } + + class AnswerCallbackImpl implements createCallEndpoint$answerCallback { + @Override + public void apply(MemorySegment opaque) { + System.err.println("TRINGBRIDGE, send answer!"); + byte[] bytes = fromJArrayByte(scope, opaque); + System.err.println("TRING, bytes to send = "+java.util.Arrays.toString(bytes)); + api.answerCallback(bytes); + System.err.println("TRING, answer sent"); + sendAck(); + System.err.println("TRING, ack sent"); + } + } + + Addressable createOfferCallback() { + OfferCallbackImpl sci = new OfferCallbackImpl(); + MemorySegment seg = createCallEndpoint$offerCallback.allocate(sci, scope); + return seg.address(); + } + + class OfferCallbackImpl implements createCallEndpoint$offerCallback { + @Override + public void apply(MemorySegment opaque) { + byte[] bytes = fromJArrayByte(scope, opaque); + api.offerCallback(bytes); + System.err.println("TRING, offer sent"); + sendAck(); + System.err.println("TRING, ack sent"); + } + } + + Addressable createIceUpdateCallback() { + IceUpdateCallbackImpl sci = new IceUpdateCallbackImpl(); + MemorySegment seg = createCallEndpoint$iceUpdateCallback.allocate(sci, scope); + return seg.address(); + } + + class IceUpdateCallbackImpl implements createCallEndpoint$iceUpdateCallback { + + @Override + public void apply(MemorySegment icePack) { + byte[] bytes = fromJArrayByte(scope, icePack); + List iceCandidates = new ArrayList<>(); + iceCandidates.add(bytes); + + api.iceUpdateCallback(iceCandidates); + sendAck(); + LOG.info("iceUpdate done!"); + } + } + MemorySegment createGenericCallback() { + GenericCallbackImpl sci = new GenericCallbackImpl(); + MemorySegment seg = createCallEndpoint$genericCallback.allocate(sci, scope); + return seg; + } + + class GenericCallbackImpl implements createCallEndpoint$genericCallback { + + @Override + public void apply(int opcode, MemorySegment data) { + byte[] bytes = fromJArrayByte(scope, data); + LOG.info("Got generic callback, opcode = " + opcode + " and data = " + Arrays.toString(bytes)); + if (opcode == 1) { + // groupId + ByteBuffer bb = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); + int groupIdLen = bb.getInt(); + byte[] groupId = new byte[groupIdLen]; + bb.get(groupId); + long ringId = bb.getLong(); + byte[] senderBytes = new byte[bb.remaining()-1]; + bb.get(senderBytes); + byte status = bb.get(); + System.err.println("GroupId = "+Arrays.toString(groupId)); + System.err.println("ringid = "+ringId); + System.err.println("senderBytes = "+Arrays.toString(senderBytes)); + System.err.println("status = "+status); + api.groupCallUpdateRing(groupId, ringId, senderBytes, status); + } + } + + } + + Addressable createVideoFrameCallback() { + VideoFrameCallbackImpl sci = new VideoFrameCallbackImpl(); + MemorySegment seg = createCallEndpoint$videoFrameCallback.allocate(sci, scope); + return seg.address(); + } + + @Deprecated + class VideoFrameCallbackImpl implements createCallEndpoint$videoFrameCallback { + @Override + public void apply(MemoryAddress opaque, int w, int h, long size) { + LOG.info("Got incoming video frame in Java layer, w = "+w+", h = " + h+", size = " + size); + System.err.println("Opaque = " + opaque); + MemorySegment segment = MemorySegment.ofAddress(opaque, size, scope); + byte[] raw = segment.toArray(ValueLayout.JAVA_BYTE); + synchronized (frameQueue) { + LOG.info("Add frame to queue"); + frameQueue.add(new TringFrame(w,h,-1,raw)); + frameQueue.notifyAll(); + } + LOG.info("Processed incoming video frame in Java layer"); + sendAck(); + } + } + + // We need to inform ringrtc that we handled a message, so that it is ok + // with sending the next message + void sendAck() { + MemorySegment callid = MemorySegment.allocateNative(8, scope); + callid.set(ValueLayout.JAVA_LONG, 0l, activeCallId); + tringlib_h.signalMessageSent(callEndpoint, callid); + } + +} diff --git a/src/java/tring/src/main/java/module-info.java b/src/java/tring/src/main/java/module-info.java new file mode 100644 index 00000000..d8cb3692 --- /dev/null +++ b/src/java/tring/src/main/java/module-info.java @@ -0,0 +1,7 @@ +module io.privacyresearch.tring { + requires java.logging; + requires io.privacyresearch.tringapi; + + exports io.privacyresearch.tring; + provides io.privacyresearch.tringapi.TringService with io.privacyresearch.tring.TringServiceImpl; +} diff --git a/src/java/tring/src/main/resources/META-INF/services/io.privacyresearch.tringapi.TringService b/src/java/tring/src/main/resources/META-INF/services/io.privacyresearch.tringapi.TringService new file mode 100644 index 00000000..6202e88e --- /dev/null +++ b/src/java/tring/src/main/resources/META-INF/services/io.privacyresearch.tringapi.TringService @@ -0,0 +1 @@ +io.privacyresearch.tring.TringServiceImpl diff --git a/src/java/tring/test.sh b/src/java/tring/test.sh new file mode 100644 index 00000000..fcc7ca2a --- /dev/null +++ b/src/java/tring/test.sh @@ -0,0 +1 @@ +java --enable-preview -cp ../tringapi/target/tringapi-0.0.8-SNAPSHOT.jar:target/tring-0.0.11-SNAPSHOT-linux-x86_64.jar io.privacyresearch.tringtest.Main diff --git a/src/java/tringapi/pom.xml b/src/java/tringapi/pom.xml new file mode 100644 index 00000000..fdd89f8c --- /dev/null +++ b/src/java/tringapi/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + io.privacyresearch + tringapi + 0.0.9-SNAPSHOT + jar + + UTF-8 + 19 + 19 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + --enable-preview + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + + false + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.2 + + + + + + gluon-nexus + https://nexus.gluonhq.com/nexus/content/repositories/releases + + + gluon-nexus + https://nexus.gluonhq.com/nexus/content/repositories/public-snapshots + + + + diff --git a/src/java/tringapi/src/main/java/io/privacyresearch/tringapi/TringApi.java b/src/java/tringapi/src/main/java/io/privacyresearch/tringapi/TringApi.java new file mode 100644 index 00000000..42837053 --- /dev/null +++ b/src/java/tringapi/src/main/java/io/privacyresearch/tringapi/TringApi.java @@ -0,0 +1,22 @@ +package io.privacyresearch.tringapi; + +import java.util.List; + +/** + * + * @author johan + */ +public interface TringApi { + + void statusCallback(long callId, long peerId, int dir, int type); + + void answerCallback(byte[] opaque); + + void offerCallback(byte[] opaque); + + void iceUpdateCallback(List iceCandidates); + + void groupCallUpdateRing(byte[] groupId, long ringId, byte[] senderBytes, byte status); + // void getVideoFrame(int w, int h, byte[] raw); + +} diff --git a/src/java/tringapi/src/main/java/io/privacyresearch/tringapi/TringBridge.java b/src/java/tringapi/src/main/java/io/privacyresearch/tringapi/TringBridge.java new file mode 100644 index 00000000..5b073b9d --- /dev/null +++ b/src/java/tringapi/src/main/java/io/privacyresearch/tringapi/TringBridge.java @@ -0,0 +1,103 @@ +package io.privacyresearch.tringapi; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Optional; +import java.util.ServiceLoader; +import java.util.logging.Logger; +import java.util.stream.Collectors; + + /** + * This class provides the access points for the application to interact with + * RingRTC. + * Methods here are invoked by Equation. + * When RingRTC wants to call back into the application, the TringApi interface + * is used. + * @author johan + */ +public class TringBridge { + + private TringService service; + private static final Logger LOG = Logger.getLogger(TringBridge.class.getName()); + + public TringBridge(final TringApi api) { + ServiceLoader loader = ServiceLoader.load(TringService.class); + Optional serviceOpt = loader.findFirst(); + serviceOpt.ifPresentOrElse(s -> { + this.service = s; + this.service.setApi(api); + }, () -> { + LOG.warning("No tring service!"); + }); + + } + + public String getVersionInfo() { + if (service == null) { + return "No TringService registered"; + } else { + return service.getVersionInfo(); + } + } + + public void acceptCall() { + service.acceptCall(); + } + + public void ignoreCall() { + service.ignoreCall(); + } + + public void hangupCall() { + service.hangupCall(); + } + + public void proceed(long callId, String iceUser, String icePassword, List ice) { + List iceb = ice.stream().map(s -> s.getBytes(StandardCharsets.UTF_8)).collect(Collectors.toList()); + service.proceed(callId, iceUser, icePassword, iceb); + } + + public void receivedIce(long callId, int senderDeviceId, List ice) { + service.receivedIce(callId, senderDeviceId, ice); + } + + public void receivedOffer(String peerId, long callId, int senderDeviceId, int receiverDeviceId, + byte[] senderKey, byte[] receiverKey, byte[] opaque) { + service.receivedOffer(peerId, callId, senderDeviceId, receiverDeviceId, senderKey, receiverKey, opaque); + } + + public void receivedOpaqueMessage(byte[] uuid, int senderDeviceId, int receiverDeviceId, + byte[] opaque, long ageMessage) { + service.receivedOpaqueMessage(uuid, senderDeviceId, receiverDeviceId, opaque, ageMessage); + } + + public void receivedAnswer(String peerId, long callId, int receiverDeviceId, + byte[] senderKey, byte[] receiverKey, byte[] opaque) { + service.receivedAnswer(peerId, callId, receiverDeviceId, senderKey, receiverKey, opaque); + } + + public long startOutgoingCall(long callId, String peerId, int localDeviceId, boolean enableVideo) { + return service.startOutgoingCall(callId, peerId, localDeviceId, enableVideo); + } + + public void enableOutgoingVideo(boolean enable) { + service.enableOutgoingVideo(enable); + } + + public TringFrame getRemoteVideoFrame() { + return service.getRemoteVideoFrame(); + } + + public void sendVideoFrame(int width, int height, int pixelFormat, byte[] raw) { + service.sendVideoFrame(width, height, pixelFormat, raw); + } + + public void setArray() { + service.setArray(); + } + + public void peekGroupCall(byte[] membershipProof) { + service.peekGroupCall(membershipProof); + } + +} diff --git a/src/java/tringapi/src/main/java/io/privacyresearch/tringapi/TringFrame.java b/src/java/tringapi/src/main/java/io/privacyresearch/tringapi/TringFrame.java new file mode 100644 index 00000000..de4e352d --- /dev/null +++ b/src/java/tringapi/src/main/java/io/privacyresearch/tringapi/TringFrame.java @@ -0,0 +1,14 @@ +package io.privacyresearch.tringapi; + +public class TringFrame { + + public final int width, height, pixelFormat; + public final byte[] data; + + public TringFrame(int width, int height, int pixelFormat, byte[] data) { + this.width = width; + this.height = height; + this.pixelFormat = pixelFormat; + this.data = data; + } +} diff --git a/src/java/tringapi/src/main/java/io/privacyresearch/tringapi/TringService.java b/src/java/tringapi/src/main/java/io/privacyresearch/tringapi/TringService.java new file mode 100644 index 00000000..9500f8f8 --- /dev/null +++ b/src/java/tringapi/src/main/java/io/privacyresearch/tringapi/TringService.java @@ -0,0 +1,60 @@ + package io.privacyresearch.tringapi; + +import java.util.List; + + /** + * Implementations of this interface provides the access points for the application to interact with + * RingRTC. + * Methods here are invoked by TringBridge, which is invoked by Equation. + * When RingRTC wants to call back into the application, the TringApi interface + * is used. + * @author johan + */ +public interface TringService { + + public void setApi(TringApi api); + + public void acceptCall(); + public void ignoreCall(); + public void hangupCall(); + + public void proceed(long callId, String iceUser, String icePwd, List ice); + + public void receivedIce(long callId, int senderDeviceId, List ice); + + public void receivedOffer(String peerId, long callId, int senderDeviceId, int receiverDeviceId, + byte[] senderKey, byte[] receiverKey, byte[] opaque); + + public void receivedOpaqueMessage(byte[] senderUuid, int senderDeviceId, + int localDeviceId, byte[] opaque, long age); + + public void receivedAnswer(String peerId, long callId, int senderDeviceId, + byte[] senderKey, byte[] receiverKey, byte[] opaque); + public long startOutgoingCall(long callId, String peerId, int localDeviceId, boolean enableVideo); + + public default String getVersionInfo() { + return "Unresolved TringService"; + } + + /** + * Disable or enable outgoing video. + * @param enable true if we want to enable outgoing video, false otherwise + */ + public void enableOutgoingVideo(boolean enable); + + /** + * Get a videoframe from the other side. + * @param skip if true, ignore all old frames, and return the most recent one + * @return a frame + */ + public TringFrame getRemoteVideoFrame(boolean skip); + + public default TringFrame getRemoteVideoFrame() { + return getRemoteVideoFrame(false); + } + public void sendVideoFrame(int w, int h, int pixelFormat, byte[] raw); + + public void setArray(); + + public void peekGroupCall(byte[] membershipProof); +} diff --git a/src/java/tringapi/src/main/java/module-info.java b/src/java/tringapi/src/main/java/module-info.java new file mode 100644 index 00000000..6d11de95 --- /dev/null +++ b/src/java/tringapi/src/main/java/module-info.java @@ -0,0 +1,6 @@ +module io.privacyresearch.tringapi { + requires java.logging; + + exports io.privacyresearch.tringapi; + uses io.privacyresearch.tringapi.TringService; +} diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 589d6299..b914119f 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "aes" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe0133578c0986e1fe3dfcd4af1cc5b2dd6c3dbf534d69916ce16a2701d40ba" +checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" dependencies = [ "cfg-if", "cipher", @@ -15,18 +15,27 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "anyhow" -version = "1.0.65" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" [[package]] name = "atty" @@ -47,9 +56,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "bitflags" @@ -68,9 +77,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.11.0" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "byteorder" @@ -80,15 +89,34 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" + +[[package]] +name = "cbindgen" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e3973b165dc0f435831a9e426de67e894de532754ff7a3f307c03ee5dec7dc" +dependencies = [ + "clap", + "heck 0.3.3", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", + "tempfile", + "toml", +] [[package]] name = "cc" -version = "1.0.73" +version = "1.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" [[package]] name = "cesu8" @@ -118,6 +146,21 @@ dependencies = [ "inout", ] +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "combine" version = "4.6.6" @@ -180,9 +223,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", @@ -197,9 +240,22 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "env_logger" -version = "0.9.1" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", "humantime", @@ -234,9 +290,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" dependencies = [ "futures-channel", "futures-core", @@ -249,9 +305,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" dependencies = [ "futures-core", "futures-sink", @@ -259,15 +315,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" [[package]] name = "futures-executor" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" dependencies = [ "futures-core", "futures-task", @@ -276,15 +332,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" [[package]] name = "futures-macro" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" dependencies = [ "proc-macro2", "quote", @@ -293,21 +349,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" [[package]] name = "futures-task" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" [[package]] name = "futures-util" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ "futures-channel", "futures-core", @@ -348,6 +404,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "heck" version = "0.4.0" @@ -387,7 +452,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -408,9 +473,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", @@ -445,9 +510,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "jni" @@ -486,9 +551,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.134" +version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" [[package]] name = "libloading" @@ -564,9 +629,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ "hermit-abi", "libc", @@ -595,9 +660,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "percent-encoding" @@ -629,9 +694,19 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "c142c0e46b57171fe0c528bee8c5b7569e80f0c17e377cd0e30ea57dbc11bb51" +dependencies = [ + "proc-macro2", + "syn", +] [[package]] name = "proc-macro-crate" @@ -646,18 +721,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ "unicode-ident", ] [[package]] name = "prost" -version = "0.11.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "399c3c31cdec40583bb68f0b18403400d01ec4289c383aa047560439952c4dd7" +checksum = "c0b18e655c21ff5ac2084a5ad0611e827b3f92badf79f4910b5a5c58f4d87ff0" dependencies = [ "bytes", "prost-derive", @@ -665,29 +740,31 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.11.1" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f835c582e6bd972ba8347313300219fed5bfa52caf175298d860b61ff6069bb" +checksum = "e330bf1316db56b12c2bcfa399e8edddd4821965ea25ddb2c134b610b1c1c604" dependencies = [ "bytes", - "heck", + "heck 0.4.0", "itertools", "lazy_static", "log", "multimap", "petgraph", + "prettyplease", "prost", "prost-types", "regex", + "syn", "tempfile", "which", ] [[package]] name = "prost-derive" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7345d5f0e08c0536d7ac7229952590239e77abf0a0100a1b1d890add6ea96364" +checksum = "164ae68b6587001ca506d3bf7f1000bfa248d0e1217b618108fba4ec1d0cc306" dependencies = [ "anyhow", "itertools", @@ -698,9 +775,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dfaa718ad76a44b3415e6c4d53b17c8f99160dcb3a99b10470fce8ad43f6e3e" +checksum = "747761bc3dc48f9a34553bf65605cf6cb6288ba219f3450b4275dbd81539551a" dependencies = [ "bytes", "prost", @@ -767,9 +844,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "aho-corasick", "memchr", @@ -797,9 +874,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" @@ -833,8 +910,10 @@ dependencies = [ "anyhow", "base64", "bytes", + "cbindgen", "ctr", - "env_logger", + "env_logger 0.8.4", + "env_logger 0.9.3", "futures", "hex", "hkdf", @@ -919,18 +998,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" +checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" +checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" dependencies = [ "proc-macro2", "quote", @@ -939,9 +1018,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.85" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" dependencies = [ "itoa", "ryu", @@ -956,7 +1035,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -986,6 +1065,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + [[package]] name = "subtle" version = "2.4.1" @@ -994,9 +1079,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.101" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" dependencies = [ "proc-macro2", "quote", @@ -1049,6 +1134,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.37" @@ -1086,13 +1180,14 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.21.2" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" dependencies = [ "autocfg", "num_cpus", "pin-project-lite", + "windows-sys", ] [[package]] @@ -1106,9 +1201,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" @@ -1118,9 +1213,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "unicode-normalization" @@ -1131,6 +1226,18 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + [[package]] name = "unicode-xid" version = "0.2.4" @@ -1170,6 +1277,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.4" @@ -1318,6 +1431,63 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + [[package]] name = "x25519-dalek" version = "1.2.0" @@ -1340,9 +1510,9 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" dependencies = [ "proc-macro2", "quote", diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 152e12f6..8ad25f7e 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -13,6 +13,7 @@ license = "AGPL-3.0-only" [lib] crate_type = ["cdylib", "staticlib", "lib"] +path = "src/lib.rs" [[bin]] name = "cli" @@ -51,6 +52,7 @@ subtle = { version = "2.3.0" } thiserror = { version = "1.0.20" } tokio = { version = "1.13.0", features = ["rt-multi-thread", "time"] } x25519-dalek = { version = "1.1" } +env_logger = { version = "0.8.1" } # Optional, needed by the "electron" feature neon = { version = "0.10.1", optional = true, default-features = false, features = ["napi-6", "channel-api"] } @@ -67,6 +69,7 @@ webpki = { version = "0.21", optional = true } default = [] sim = [] electron = ["neon", "native"] +java = ["native"] native = [] # We have this so we can more easily disable things only native clients need simnet = [] # We have this so we can more easily disable things only simulated native client need http = ["ureq", "rustls", "webpki"] @@ -86,6 +89,7 @@ required-features = ["sim"] jni = { version = "0.19.0", default-features = false } [build-dependencies] +cbindgen = "0.20.0" prost-build = { version = "0.11" } [dev-dependencies] diff --git a/src/rust/build.rs b/src/rust/build.rs index 9fc1c783..ed156580 100644 --- a/src/rust/build.rs +++ b/src/rust/build.rs @@ -21,6 +21,15 @@ fn build_protos() { } fn main() { + if cfg!(feature = "java") { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_language(cbindgen::Language::C) + .generate() + .expect("unable to generate bindings") + .write_to_file("tringlib.h"); + } let target = env::var("TARGET").unwrap(); let profile = env::var("PROFILE").unwrap(); let out_dir = env::var("OUTPUT_DIR"); diff --git a/src/rust/src/common/mod.rs b/src/rust/src/common/mod.rs index 1cfd1ad6..0d271d36 100644 --- a/src/rust/src/common/mod.rs +++ b/src/rust/src/common/mod.rs @@ -16,6 +16,7 @@ pub type Result = std::result::Result; /// Unique call identification number. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[repr(C)] pub struct CallId { id: u64, } diff --git a/src/rust/src/core/connection.rs b/src/rust/src/core/connection.rs index b78ece67..99880e66 100644 --- a/src/rust/src/core/connection.rs +++ b/src/rust/src/core/connection.rs @@ -2062,6 +2062,7 @@ where _video_frame_metadata: VideoFrameMetadata, video_frame: Option, ) -> Result<()> { + info!("Hello, incoming video frame"); if let (Some(incoming_video_sink), Some(video_frame)) = (self.incoming_video_sink.as_ref(), video_frame) { diff --git a/src/rust/src/java/java.rs b/src/rust/src/java/java.rs new file mode 100644 index 00000000..bf7359b3 --- /dev/null +++ b/src/rust/src/java/java.rs @@ -0,0 +1,1032 @@ +#![allow(unused_parens)] + +use std::collections::HashMap; +use std::convert::TryInto; + +use std::slice; +use std::sync::atomic::AtomicBool; +use std::sync::mpsc::{channel, Sender}; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use libc::size_t; + +use crate::common::{CallDirection, CallId, CallMediaType, DeviceId, Result}; +use crate::core::bandwidth_mode::BandwidthMode; +use crate::core::call_manager::CallManager; +use crate::core::group_call; +use crate::core::group_call::{GroupId, SignalingMessageUrgency}; +use crate::core::signaling; +use crate::core::util::ptr_as_mut; + +use crate::java::jtypes::{ + JArrayByte, JArrayByte2D, JByteArray, JByteArray2D, JString, TringDevice, +}; + +use crate::lite::http; +use crate::lite::sfu::UserId; +use crate::native::{ + CallState, CallStateHandler, GroupUpdate, GroupUpdateHandler, NativeCallContext, + NativePlatform, PeerId, SignalingSender, +}; +use crate::webrtc::logging; +use crate::webrtc::media::{ + AudioTrack, VideoFrame, VideoPixelFormat, VideoSink, VideoSource, VideoTrack, +}; + +use crate::webrtc::peer_connection::AudioLevel; + +use crate::webrtc::peer_connection_factory::{ + self as pcf, AudioDevice, IceServer, PeerConnectionFactory, +}; +use crate::webrtc::peer_connection_observer::NetworkRoute; + +fn init_logging() { + env_logger::builder() + .filter(None, log::LevelFilter::Debug) + .init(); + println!("LOGINIT done"); + // let is_first_time_initializing_logger = log::set_logger(&LOG).is_ok(); + let is_first_time_initializing_logger = true; + println!("EXTRALOG? {}", is_first_time_initializing_logger); + if is_first_time_initializing_logger { + // log::set_max_level(log::LevelFilter::Debug); + logging::set_logger(log::LevelFilter::Warn); + println!("EXTRALOG? yes"); + } + // logging::set_logger(log::LevelFilter::Trace); + info!("INFO logging enabled"); +} + +// When the Java layer processes events, we want everything to go through a common queue that +// combines all the things we want to "push" to it. +pub enum Event { + // The JavaScript should send the following signaling message to the given + // PeerId in context of the given CallId. If the DeviceId is None, then + // broadcast to all devices of that PeerId. + SendSignaling(PeerId, Option, CallId, signaling::Message), + // The JavaScript should send the following opaque call message to the + // given recipient UUID. + SendCallMessage { + recipient_uuid: UserId, + message: Vec, + urgency: group_call::SignalingMessageUrgency, + }, + // The JavaScript should send the following opaque call message to all + // other members of the given group + SendCallMessageToGroup { + group_id: GroupId, + message: Vec, + urgency: group_call::SignalingMessageUrgency, + }, + // The call with the given remote PeerId has changed state. + // We assume only one call per remote PeerId at a time. + CallState(PeerId, CallId, CallState), + // The state of the remote video (whether enabled or not) changed. + // Like call state, we ID the call by PeerId and assume there is only one. + RemoteVideoStateChange(PeerId, bool), + // Whether the remote is sharing its screen or not changed. + // Like call state, we ID the call by PeerId and assume there is only one. + RemoteSharingScreenChange(PeerId, bool), + // The group call has an update. + GroupUpdate(GroupUpdate), + // JavaScript should initiate an HTTP request. + SendHttpRequest { + request_id: u32, + request: http::Request, + }, + // The network route changed for a 1:1 call + NetworkRouteChange(PeerId, NetworkRoute), + AudioLevels { + peer_id: PeerId, + captured_level: AudioLevel, + received_level: AudioLevel, + }, +} + +/// Wraps a [`std::sync::mpsc::Sender`] with a callback to report new events. +#[derive(Clone)] +#[repr(C)] +#[allow(non_snake_case)] +struct EventReporter { + pub statusCallback: unsafe extern "C" fn(u64, u64, i32, i32), + pub answerCallback: unsafe extern "C" fn(JArrayByte), + pub offerCallback: unsafe extern "C" fn(JArrayByte), + pub iceUpdateCallback: unsafe extern "C" fn(JArrayByte), + pub genericCallback: unsafe extern "C" fn(i32, JArrayByte), + sender: Sender, + report: Arc, +} + +fn string_to_bytes(v:String) -> Vec { + let mut answer:Vec = Vec::new(); + let ul = v.len(); + answer.extend_from_slice(&ul.to_le_bytes()); + answer.extend_from_slice(v.as_bytes()); + answer +} + +impl EventReporter { + fn new( + statusCallback: extern "C" fn(u64, u64, i32, i32), + answerCallback: extern "C" fn(JArrayByte), + offerCallback: extern "C" fn(JArrayByte), + iceUpdateCallback: extern "C" fn(JArrayByte), + genericCallback: extern "C" fn(i32, JArrayByte), + sender: Sender, + report: impl Fn() + Send + Sync + 'static, + ) -> Self { + Self { + statusCallback, + answerCallback, + offerCallback, + iceUpdateCallback, + genericCallback, + sender, + report: Arc::new(report), + } + } + + fn send(&self, event: Event) -> Result<()> { + match event { + Event::SendSignaling(_peer_id, _maybe_device_id, call_id, signal) => { + info!("JavaPlatform needs to send SignalingEvent to app"); + match signal { + signaling::Message::Offer(offer) => { + info!( + "[JV] SendSignaling OFFER Event and call_id = {}", + call_id.as_u64() + ); + let op = JArrayByte::new(offer.opaque); + unsafe { + (self.offerCallback)(op); + } + } + signaling::Message::Answer(answer) => { + info!("[JV] SendSignaling ANSWER Event"); + let op = JArrayByte::new(answer.opaque); + unsafe { + (self.answerCallback)(op); + } + } + signaling::Message::Ice(ice) => { + info!("[JV] SendSignaling ICE Event"); + let ilen = ice.candidates.len(); + unsafe { + for i in 0..ilen { + (self.iceUpdateCallback)(JArrayByte::new( + ice.candidates[i].opaque.clone(), + )); + } + } + } + signaling::Message::Hangup(hangup) => { + let (hangup_type, hangup_device_id) = hangup.to_type_and_device_id(); + let device_id: u64 = match hangup_device_id { + Some(device_id) => device_id.into(), + None => 0, + }; + info!("[JV] SendSignaling Hangup Event"); + unsafe { + (self.statusCallback)( + call_id.as_u64(), + device_id, + 11, + hangup_type as i32, + ); + } + } + _ => { + info!("[JV] unknownSendSignalingEvent WHICH IS WHAT WE NEED TO FIX NOW!"); + } + } + info!("JavaPlatform asked app to send SignalingEvent"); + } + Event::CallState(_peer_id, call_id, CallState::Incoming(call_media_type)) => { + info!("[JV] CALLSTATEEVEMNT"); + let direction = 0; + unsafe { + (self.statusCallback)(call_id.as_u64(), 1, direction, call_media_type as i32); + } + } + Event::CallState(_peer_id, call_id, CallState::Outgoing(call_media_type)) => { + info!("[JV] CALLSTATEEVEMNT"); + let direction = 1; + unsafe { + (self.statusCallback)(call_id.as_u64(), 1, direction, call_media_type as i32); + } + } + Event::CallState(_peer_id, call_id, state) => { + info!("[JV] CallState changed"); + let (state_string, state_index) = match state { + CallState::Ringing => ("ringing", 1), + CallState::Connected => ("connected", 2), + CallState::Connecting => ("connecting", 3), + CallState::Concluded => ("Concluded", 4), + CallState::Incoming(_) => ("incoming", 5), + CallState::Outgoing(_) => ("outgoing", 6), + CallState::Ended(_) => ("ended", 7), + }; + info!("New state = {} and index = {}", state_string, state_index); + unsafe { + (self.statusCallback)(call_id.as_u64(), 1, 10 * state_index, 0); + } + } + Event::RemoteVideoStateChange(peer_id, enabled) => { + info!("RemoveVideoStateChange to {}", enabled); + unsafe { + if enabled { + (self.statusCallback)(1, 1, 22, 31); + } else { + (self.statusCallback)(1, 1, 22, 32); + } + } + } + Event::SendHttpRequest { + request_id, + request: + http::Request { + method, + url, + headers, + body, + }, + } => { + info!("NYI SendHttpReq"); + info!("Request id = {}", request_id); + info!("Requestmethod = {:?}", method); + info!("Requesturl = {:?}", url); + info!("Requestheaders = {:?}", headers); + info!("Requestbody = {:?}", body); + let mut payload:Vec = Vec::new(); + let rid = request_id as i32; + payload.extend_from_slice(&rid.to_le_bytes()); + + payload.push(method as u8); + + payload.extend(string_to_bytes(url)); + + let mut hdr:Vec = Vec::new(); + for (name, value) in headers.iter() { + hdr.extend(string_to_bytes(name.to_string())); + hdr.extend(string_to_bytes(value.to_string())); + } + let hdrlen = hdr.len(); + payload.extend_from_slice(&hdrlen.to_le_bytes()); + payload.extend(hdr); + + let bl = body.as_ref().map_or(0, |v| v.len()); + payload.extend_from_slice(&bl.to_le_bytes()); + payload.extend(body.unwrap_or_default()); + let data = JArrayByte::new(payload); + unsafe { + (self.genericCallback)(2, data); + } + } + Event::GroupUpdate(GroupUpdate::RequestMembershipProof(client_id)) => { + info!("NYI RMP"); + } + Event::GroupUpdate(GroupUpdate::RequestGroupMembers(client_id)) => { + info!("NYI RGM"); + } + Event::GroupUpdate(GroupUpdate::ConnectionStateChanged( + client_id, + connection_state, + )) => { + info!("NYI CSTATEChanged"); + } + Event::GroupUpdate(GroupUpdate::NetworkRouteChanged(client_id, network_route)) => { + info!("NYI NetworkRouteChanged"); + } + Event::GroupUpdate(GroupUpdate::JoinStateChanged(client_id, join_state)) => { + info!("NYI JoinStatesChanged"); + } + Event::GroupUpdate(GroupUpdate::RemoteDeviceStatesChanged( + client_id, + remote_device_states, + )) => { + info!("NYI RemoteDeviceStatesChanged"); + } + Event::GroupUpdate(GroupUpdate::PeekChanged { + client_id, + peek_info, + }) => { + info!("NYI PeekChanged"); + } + Event::GroupUpdate(GroupUpdate::PeekResult { + request_id, + peek_result, + }) => { + info!("NYI PeekResult"); + } + Event::GroupUpdate(GroupUpdate::Ended(client_id, reason)) => { + info!("NYI ENDED"); + } + Event::GroupUpdate(GroupUpdate::Ring { + group_id, + ring_id, + sender, + update, + }) => { + info!("[JV] GroupUpdate, gid = {:?}, ringid = {:?}, sender = {:?}, update = {:?}", group_id, ring_id, sender, update); + let mut payload:Vec = Vec::new(); + let glen: i32 = group_id.len().try_into().unwrap(); + payload.extend_from_slice(&glen.to_le_bytes()); + payload.extend(group_id); + let rid: i64 = ring_id.into(); + payload.extend_from_slice(&rid.to_le_bytes()); + payload.extend(sender); + payload.push(update as u8); + let data = JArrayByte::new(payload); + unsafe { + (self.genericCallback)(1, data); + } + } + _ => { + info!("[JV] unknownevent"); + } + }; + + Ok(()) + } + + fn report(&self) { + (self.report)(); + } +} + +impl SignalingSender for EventReporter { + fn send_signaling( + &self, + recipient_id: &str, + call_id: CallId, + receiver_device_id: Option, + msg: signaling::Message, + ) -> Result<()> { + info!("Need to send SIGNALING msg {:?}", msg); + self.send(Event::SendSignaling( + recipient_id.to_string(), + receiver_device_id, + call_id, + msg, + ))?; + Ok(()) + } + + fn send_call_message( + &self, + recipient_uuid: UserId, + message: Vec, + urgency: SignalingMessageUrgency, + ) -> Result<()> { + self.send(Event::SendCallMessage { + recipient_uuid, + message, + urgency, + })?; + Ok(()) + } + + fn send_call_message_to_group( + &self, + group_id: GroupId, + message: Vec, + urgency: group_call::SignalingMessageUrgency, + ) -> Result<()> { + self.send(Event::SendCallMessageToGroup { + group_id, + message, + urgency, + })?; + Ok(()) + } +} + +impl CallStateHandler for EventReporter { + fn handle_call_state( + &self, + remote_peer_id: &str, + call_id: CallId, + call_state: CallState, + ) -> Result<()> { + info!("[JV] CallStatehandler, invoke self.send"); + + self.send(Event::CallState( + remote_peer_id.to_string(), + call_id, + call_state, + ))?; + Ok(()) + } + + fn handle_network_route( + &self, + remote_peer_id: &str, + network_route: NetworkRoute, + ) -> Result<()> { + self.send(Event::NetworkRouteChange( + remote_peer_id.to_string(), + network_route, + ))?; + Ok(()) + } + + fn handle_remote_video_state(&self, remote_peer_id: &str, enabled: bool) -> Result<()> { + self.send(Event::RemoteVideoStateChange( + remote_peer_id.to_string(), + enabled, + ))?; + Ok(()) + } + + fn handle_remote_sharing_screen(&self, remote_peer_id: &str, enabled: bool) -> Result<()> { + self.send(Event::RemoteSharingScreenChange( + remote_peer_id.to_string(), + enabled, + ))?; + Ok(()) + } + + fn handle_audio_levels( + &self, + remote_peer_id: &str, + captured_level: AudioLevel, + received_level: AudioLevel, + ) -> Result<()> { + self.send(Event::AudioLevels { + peer_id: remote_peer_id.to_string(), + captured_level, + received_level, + })?; + Ok(()) + } +} + +impl http::Delegate for EventReporter { + fn send_request(&self, request_id: u32, request: http::Request) { + let _ = self.send(Event::SendHttpRequest { + request_id, + request, + }); + } +} + +impl GroupUpdateHandler for EventReporter { + fn handle_group_update(&self, update: GroupUpdate) -> Result<()> { + info!("Handle group update {:?}", update); + self.send(Event::GroupUpdate(update))?; + Ok(()) + } +} + +pub struct CallEndpoint { + call_manager: CallManager, + // This is what we use to control mute/not. + // It should probably be per-call, but for now it's easier to have only one. + outgoing_audio_track: AudioTrack, + // This is what we use to push video frames out. + outgoing_video_source: VideoSource, + // We only keep this around so we can pass it to PeerConnectionFactory::create_peer_connection + // via the NativeCallContext. + outgoing_video_track: VideoTrack, + // Boxed so we can pass it as a Box + incoming_video_sink: Box, + peer_connection_factory: PeerConnectionFactory, + videoFrameCallback: extern "C" fn(*const u8, u32, u32, size_t), +} + +impl CallEndpoint { + fn new<'a>( + use_new_audio_device_module: bool, + statusCallback: extern "C" fn(u64, u64, i32, i32), + answerCallback: extern "C" fn(JArrayByte), + offerCallback: extern "C" fn(JArrayByte), + iceUpdateCallback: extern "C" fn(JArrayByte), + genericCallback: extern "C" fn(i32, JArrayByte), + videoFrameCallback: extern "C" fn(*const u8, u32, u32, size_t), + ) -> Result { + // Relevant for both group calls and 1:1 calls + let (events_sender, _events_receiver) = channel::(); + let peer_connection_factory = PeerConnectionFactory::new(pcf::Config { + use_new_audio_device_module, + ..Default::default() + })?; + let outgoing_audio_track = peer_connection_factory.create_outgoing_audio_track()?; + outgoing_audio_track.set_enabled(false); + let outgoing_video_source = peer_connection_factory.create_outgoing_video_source()?; + let outgoing_video_track = + peer_connection_factory.create_outgoing_video_track(&outgoing_video_source)?; + outgoing_video_track.set_enabled(false); + let incoming_video_sink = Box::new(LastFramesVideoSink::default()); + + let event_reported = Arc::new(AtomicBool::new(false)); + + let event_reporter = EventReporter::new( + statusCallback, + answerCallback, + offerCallback, + iceUpdateCallback, + genericCallback, + events_sender, + move || { + info!("[JV] EVENT_REPORTER, NYI"); + if event_reported.swap(true, std::sync::atomic::Ordering::Relaxed) { + return; + } + }, + ); + // Only relevant for 1:1 calls + let signaling_sender = Box::new(event_reporter.clone()); + let should_assume_messages_sent = false; // Use async notification from app to send next message. + let state_handler = Box::new(event_reporter.clone()); + + // Only relevant for group calls + let http_client = http::DelegatingClient::new(event_reporter.clone()); + let group_handler = Box::new(event_reporter); + + let platform = NativePlatform::new( + peer_connection_factory.clone(), + signaling_sender, + should_assume_messages_sent, + state_handler, + group_handler, + ); + + let call_manager = CallManager::new(platform, http_client)?; + Ok(Self { + call_manager, + outgoing_audio_track, + outgoing_video_source, + outgoing_video_track, + incoming_video_sink, + peer_connection_factory, + videoFrameCallback, + }) + } +} + +#[derive(Clone, Default)] +struct LastFramesVideoSink { + last_frame_by_track_id: Arc>>, +} + +impl VideoSink for LastFramesVideoSink { + fn on_video_frame(&self, track_id: u32, frame: VideoFrame) { + info!("Got videoframe!"); + // let myframe: &mut[u8;512] = &mut [0;512]; + // frame.to_rgba(myframe.as_mut_slice()); + // info!("uploading frame = {:?}", myframe); + // info!("frame uploaded"); + self.last_frame_by_track_id + .lock() + .unwrap() + .insert(track_id, frame); + } + + fn box_clone(&self) -> Box { + Box::new(self.clone()) + } +} + +impl LastFramesVideoSink { + fn pop(&self, track_id: u32) -> Option { + self.last_frame_by_track_id + .lock() + .unwrap() + .remove(&track_id) + } + + fn clear(&self) { + self.last_frame_by_track_id.lock().unwrap().clear(); + } +} + +#[no_mangle] +pub unsafe extern "C" fn initRingRTC(ts: JString) -> i64 { + println!("Initialize RingRTC, init logging"); + init_logging(); + println!("Initialize RingRTC, init logging done"); + println!("Ready to print {:?}", ts); + let txt = ts.to_string(); + info!("Got text: {}", txt); + info!("Initialized RingRTC, using logging"); + 1 +} + +#[no_mangle] +pub unsafe extern "C" fn getVersion() -> i64 { + 1 +} + +#[no_mangle] +pub unsafe extern "C" fn createCallEndpoint( + statusCallback: extern "C" fn(u64, u64, i32, i32), + answerCallback: extern "C" fn(JArrayByte), + offerCallback: extern "C" fn(JArrayByte), + iceUpdateCallback: extern "C" fn(JArrayByte), + genericCallback: extern "C" fn(i32, JArrayByte), + videoFrameCallback: extern "C" fn(*const u8, u32, u32, size_t), +) -> i64 { + let call_endpoint = CallEndpoint::new( + false, + statusCallback, + answerCallback, + offerCallback, + iceUpdateCallback, + genericCallback, + videoFrameCallback, + ) + .unwrap(); + let call_endpoint_box = Box::new(call_endpoint); + let boxx: Result<*mut CallEndpoint> = Ok(Box::into_raw(call_endpoint_box)); + + let answer: i64 = match boxx { + Ok(v) => v as i64, + Err(e) => { + info!("Error creating callEndpoint: {}", e); + 0 + } + }; + info!("[tring] CallEndpoint created at {}", answer); + answer +} + +#[no_mangle] +pub unsafe extern "C" fn setSelfUuid(endpoint: i64, ts: JString) -> i64 { + let txt = ts.to_string(); + info!("setSelfUuid to : {}", txt); + let uuid = txt.into_bytes(); + let callendpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + callendpoint.call_manager.set_self_uuid(uuid); + 1 +} + +#[no_mangle] +pub unsafe extern "C" fn receivedOffer( + endpoint: i64, + peerId: JString, + call_id: u64, + offer_type: i32, + sender_device_id: u32, + receiver_device_id: u32, + sender_key: JByteArray, + receiver_key: JByteArray, + opaque: JByteArray, + age_sec: u64, +) -> i64 { + let callendpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + let peer_id = JString::from(peerId); + let call_id = CallId::new(call_id); + let call_media_type = match offer_type { + 1 => CallMediaType::Video, + _ => CallMediaType::Audio, // TODO: Do something better. Default matches are evil. + }; + let offer = signaling::Offer::new(call_media_type, opaque.to_vec_u8()).unwrap(); + callendpoint.call_manager.received_offer( + peer_id.to_string(), + call_id, + signaling::ReceivedOffer { + offer, + age: Duration::from_secs(age_sec), + sender_device_id, + receiver_device_id, + // A Java desktop client cannot be the primary device. + receiver_device_is_primary: false, + sender_identity_key: sender_key.to_vec_u8(), + receiver_identity_key: receiver_key.to_vec_u8(), + }, + ); + 1 +} + +#[no_mangle] +pub unsafe extern "C" fn receivedOpaqueMessage( + endpoint: i64, + sender_juuid: JByteArray, + sender_device_id: DeviceId, + local_device_id: DeviceId, + opaque: JByteArray, + message_age_sec: u64) -> i64 { + info!("Create opaque message!"); + let message = opaque.to_vec_u8(); + let sender_uuid = sender_juuid.to_vec_u8(); + let callendpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + callendpoint.call_manager.received_call_message(sender_uuid, sender_device_id, local_device_id, message, Duration::from_secs(message_age_sec)); +1 +} + +#[no_mangle] +pub unsafe extern "C" fn receivedAnswer( + endpoint: i64, + peerId: JString, + call_id: u64, + sender_device_id: u32, + sender_key: JByteArray, + receiver_key: JByteArray, + opaque: JByteArray, +) -> i64 { + let callendpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + let peer_id = JString::from(peerId); + let call_id = CallId::new(call_id); + let answer = signaling::Answer::new(opaque.to_vec_u8()).unwrap(); + callendpoint.call_manager.received_answer( + call_id, + signaling::ReceivedAnswer { + answer, + sender_device_id, + sender_identity_key: sender_key.to_vec_u8(), + receiver_identity_key: receiver_key.to_vec_u8(), + }, + ); + 1 +} + +// suppy a random callid +#[no_mangle] +pub unsafe extern "C" fn createOutgoingCall( + endpoint: i64, + peer_id: JString, + video_enabled: bool, + local_device_id: u32, + call_id: i64, +) -> i64 { + info!("create outgoing call"); + let endpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + let peer_id = peer_id.to_string(); + let media_type = if video_enabled { + CallMediaType::Video + } else { + CallMediaType::Audio + }; + let call_id = CallId::from(call_id); + endpoint + .call_manager + .create_outgoing_call(peer_id, call_id, media_type, local_device_id); + 1 +} + +#[no_mangle] +pub unsafe extern "C" fn proceedCall( + endpoint: i64, + call_id: u64, + bandwidth_mode: i32, + audio_levels_interval_millis: i32, + ice_user: JString, + ice_pwd: JString, + icepack: JByteArray2D, +) -> i64 { + info!("Proceeding with call"); + let endpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + let call_id = CallId::from(call_id); + let mut ice_candidates = Vec::new(); + for j in 0..icepack.len { + let row = &icepack.buff[j]; + let opaque = row.to_vec_u8(); + ice_candidates.push(String::from_utf8(opaque).unwrap()); + } + let ice_server = IceServer::new(ice_user.to_string(), ice_pwd.to_string(), ice_candidates); + let context = NativeCallContext::new( + false, + ice_server, + endpoint.outgoing_audio_track.clone(), + endpoint.outgoing_video_track.clone(), + endpoint.incoming_video_sink.clone(), + ); + let audio_levels_interval = if audio_levels_interval_millis <= 0 { + None + } else { + Some(Duration::from_millis(audio_levels_interval_millis as u64)) + }; + endpoint.call_manager.proceed( + call_id, + context, + BandwidthMode::from_i32(bandwidth_mode), + audio_levels_interval, + ); + + 147 +} + +#[no_mangle] +pub unsafe extern "C" fn receivedIce( + endpoint: i64, + call_id: u64, + sender_device_id: DeviceId, + icepack: JByteArray2D, +) { + info!("receivedIce from app with length = {}", icepack.len); + let callendpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + let call_id = CallId::from(call_id); + let mut ice_candidates = Vec::new(); + for j in 0..icepack.len { + let row = &icepack.buff[j]; + let opaque = row.to_vec_u8(); + ice_candidates.push(signaling::IceCandidate::new(opaque)); + } + callendpoint.call_manager.received_ice( + call_id, + signaling::ReceivedIce { + ice: signaling::Ice { + candidates: ice_candidates, + }, + sender_device_id, + }, + ); + info!("receivedIce invoked call_manager and will now return to app"); +} + +#[no_mangle] +pub unsafe extern "C" fn acceptCall(endpoint: i64, call_id: u64) -> i64 { + let endpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + info!("acceptCall requested by app"); + let call_id = CallId::from(call_id); + endpoint.call_manager.accept_call(call_id); + 573 +} + +#[no_mangle] +pub unsafe extern "C" fn ignoreCall(endpoint: i64, call_id: u64) -> i64 { + let endpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + info!("now drop (ignore) call"); + let call_id = CallId::from(call_id); + endpoint.call_manager.drop_call(call_id); + 1 +} + +#[no_mangle] +pub unsafe extern "C" fn hangupCall(endpoint: i64) -> i64 { + let endpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + info!("now hangup call"); + endpoint.call_manager.hangup(); + 1 +} + +#[no_mangle] +pub unsafe extern "C" fn signalMessageSent(endpoint: i64, call_id: CallId) -> i64 { + let callendpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + info!("Received signalmessagesent, endpoint = {:?}", endpoint); + callendpoint.call_manager.message_sent(call_id); + 135 +} + +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn getAudioInputs(endpoint: i64, idx: u32) -> TringDevice<'static> { + let callendpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + let devices = callendpoint + .peer_connection_factory + .get_audio_recording_devices() + .unwrap(); + // let mut answer: [TringDevice;16] = [TringDevice::empty();16]; + let mut answer: TringDevice = TringDevice::empty(); + for (i, device) in devices.iter().enumerate() { + let wd = TringDevice::from_fields( + i as u32, + device.name.clone(), + device.unique_id.clone(), + device.i18n_key.clone(), + ); + if (i as u32 == idx) { + answer = wd; + } + // answer[i] = wd; + } + answer +} + +#[no_mangle] +pub unsafe extern "C" fn setAudioInput(endpoint: i64, index: u16) -> i64 { + let endpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + info!("Have to set audio_recordig_device to {}", index); + endpoint + .peer_connection_factory + .set_audio_recording_device(index); + 1 +} + +#[no_mangle] +pub unsafe extern "C" fn getAudioOutputs(endpoint: i64) -> i64 { + let callendpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + let devices = callendpoint + .peer_connection_factory + .get_audio_playout_devices(); + + for device in devices.iter() { + info!("OUTDEVICE = {:#?}", device); + } + 1 +} + +#[no_mangle] +pub unsafe extern "C" fn setAudioOutput(endpoint: i64, index: u16) -> i64 { + let endpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + info!("Have to set audio_output_device to {}", index); + endpoint + .peer_connection_factory + .set_audio_playout_device(index); + 1 +} + +#[no_mangle] +pub unsafe extern "C" fn setOutgoingAudioEnabled(endpoint: i64, enable: bool) -> i64 { + let endpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + info!("Have to set outgoing audio enabled to {}", enable); + endpoint.outgoing_audio_track.set_enabled(enable); + 1 +} + +#[no_mangle] +pub unsafe extern "C" fn setOutgoingVideoEnabled(endpoint: i64, enable: bool) -> i64 { + info!("Hava to setOutgoingVideoEnabled({})", enable); + let endpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + endpoint.outgoing_video_track.set_enabled(enable); + let mut active_connection = endpoint.call_manager.active_connection(); + if (active_connection.is_ok()) { + active_connection + .expect("No active connection!") + .update_sender_status(signaling::SenderStatus { + video_enabled: Some(enable), + ..Default::default() + }); + } else { + info!("No active connection") + } + 1 +} + +#[no_mangle] +pub unsafe extern "C" fn sendVideoFrame( + endpoint: i64, + width: u32, + height: u32, + pixel_format: i32, + raw: *const u8, +) -> i64 { + let endpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + let mut size = width * height * 2; + if (pixel_format == 1) { + size = size * 2; + } + info!( + "Will send VideoFrame, width = {}, heigth = {}, pixelformat = {}, size = {}", + width, height, pixel_format, size + ); + let buffer: &[u8] = unsafe { slice::from_raw_parts(raw, size as usize) }; + + let pixel_format = VideoPixelFormat::from_i32(pixel_format); + let pixel_format = pixel_format.unwrap(); + info!( + "buf[0] = {} and buf[1] = {} and buf[300] = {}, size = {}", + buffer[0], buffer[1], buffer[300], size + ); + let frame = VideoFrame::copy_from_slice(width, height, pixel_format, buffer); + endpoint.outgoing_video_source.push_frame(frame); + 1 +} + +#[no_mangle] +pub unsafe extern "C" fn fillLargeArray(endpoint: i64, mybuffer: *mut u8) -> i64 { + let zero = *mybuffer.offset(0); + let first = *mybuffer.offset(1); + let second = *mybuffer.offset(12); + info!("VAL 1 = {} and VAL2 = {}", first, second); + *mybuffer.offset(12) = 13; + 1 +} + +#[no_mangle] +pub unsafe extern "C" fn fillRemoteVideoFrame(endpoint: i64, mybuffer: *mut u8, len: usize) -> i64 { + info!("Have to retrieve remote video frame"); + let endpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + let frame = endpoint.incoming_video_sink.pop(0); + if let Some(frame) = frame { + let frame = frame.apply_rotation(); + let width: u32 = frame.width(); + let height: u32 = frame.height(); + let myframe: &mut [u8] = slice::from_raw_parts_mut(mybuffer, len); + frame.to_rgba(myframe); + info!( + "Frame0 = {}, w = {}, h = {}", + myframe[0], + frame.width(), + frame.height() + ); + let mut size: i64 = (frame.width() << 16).into(); + size = size + frame.height() as i64; + size + } else { + 0 + } +} + +#[no_mangle] +pub unsafe extern "C" fn peekGroupCall(endpoint: i64, + mp: JByteArray, +) -> i64 { + let membership_proof = mp.to_vec_u8(); + let endpoint = ptr_as_mut(endpoint as *mut CallEndpoint).unwrap(); + info!("peekGroupCall, not fully implemented"); + let sfu = String::from("https://sfu.voip.signal.org"); + endpoint.call_manager.peek_group_call(1, sfu, membership_proof, Vec::new()); + 1 +} diff --git a/src/rust/src/java/jtypes.rs b/src/rust/src/java/jtypes.rs new file mode 100644 index 00000000..88a1a430 --- /dev/null +++ b/src/rust/src/java/jtypes.rs @@ -0,0 +1,249 @@ +#![allow(unused_parens)] + +use crate::core::signaling; +use crate::webrtc::peer_connection_factory::AudioDevice; + +use core::slice; +use std::fmt; + +#[repr(C)] +#[derive(Debug)] +pub struct JString { + len: usize, + buff: *mut u8, +} + +impl JString { + pub fn to_string(&self) -> String { + let answer = unsafe { String::from_raw_parts(self.buff, self.len, self.len) }; + answer + } + + /* + pub fn from_string(src: String) -> Self { + let string_len = src.len(); + let mut string_bytes = src.as_bytes().as_mut_ptr(); + Self { + len: string_len, + buff: string_bytes + } + } + */ +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct RString<'a> { + len: usize, + buff: *const u8, + phantom: std::marker::PhantomData<&'a u8>, +} + +impl<'a> RString<'a> { + pub fn from_string(src: String) -> Self { + let string_len = src.len(); + let mut string_bytes = src.as_bytes().as_ptr(); + Self { + len: string_len, + buff: string_bytes, + phantom: std::marker::PhantomData, + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct JArrayByte { + pub len: usize, + pub data: [u8; 256], +} + +impl fmt::Display for JArrayByte { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "JArrayByte with {} bytes at {:?}", + self.len, + &(self.data) + ) + } +} +impl JArrayByte { + pub fn new(vector: Vec) -> Self { + let vlen = vector.len(); + let mut vdata = [0; 256]; + for i in 0..vlen { + vdata[i] = vector[i]; + } + JArrayByte { + len: vlen, + data: vdata, + } + } + + pub fn empty() -> Self { + let data = [0; 256]; + JArrayByte { len: 0, data: data } + } +} + +#[repr(C)] +#[derive(Debug)] +pub struct JArrayByte2D { + pub len: usize, + pub data: [u8; 256], + // pub data: [JArrayByte;25], +} + +impl JArrayByte2D { + pub fn new(vector: Vec) -> Self { + info!( + "I have to create a jArrayByte with {} elements", + vector.len() + ); + let vlen = vector.len(); + let mut myrows: [JArrayByte; 25] = [JArrayByte::empty(); 25]; + for i in 0..25 { + if (i < vlen) { + myrows[i] = JArrayByte::new(vector[i].opaque.clone()); + // myrows[i] = JByteArray::from_data(vector[i].opaque.as_ptr(), vector[i].opaque.len()); + info!("IceVec[{}] = {:?}", i, vector[i].opaque); + } else { + // myrows[i] = JByteArray::new(Vec::new()); + myrows[i] = JArrayByte::new(Vec::new()); + } + info!("Myrow[{}] : {}", i, myrows[i]); + } + info!("data at {:?}", myrows); + JArrayByte2D { + len: vlen, + data: [1; 256], + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct JByteArray { + len: usize, + pub buff: *const u8, +} + +impl fmt::Display for JByteArray { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let address = &self.buff; + write!(f, "jByteArray with {} bytes at {:p}", self.len, self.buff) + } +} + +impl JByteArray { + pub fn new(vector: Vec) -> Self { + let slice = vector.as_slice(); + let buffer = slice.as_ptr(); + JByteArray { + len: vector.len(), + buff: buffer, + } + } + + pub fn to_vec_u8(&self) -> Vec { + let answer = unsafe { slice::from_raw_parts(self.buff, self.len).to_vec() }; + answer + } + + pub fn empty() -> Self { + let bar = Vec::new().as_ptr(); + JByteArray { len: 0, buff: bar } + } + + pub fn from_data(data: *const u8, len: usize) -> Self { + JByteArray { + len: len, + buff: data, + } + } +} + +#[repr(C)] +#[derive(Debug)] +pub struct JByteArray2D { + pub len: usize, + pub buff: [JByteArray; 32], +} + +impl JByteArray2D { + pub fn new(vector: Vec) -> Self { + let vlen = vector.len(); + // let mut myrows = [Opaque::empty(); 25]; + let mut myrows: [JByteArray; 32] = [JByteArray::empty(); 32]; + for i in 0..25 { + if (i < vlen) { + myrows[i] = + JByteArray::from_data(vector[i].opaque.as_ptr(), vector[i].opaque.len()); + } else { + myrows[i] = JByteArray::new(Vec::new()); + } + } + JByteArray2D { + len: vlen, + buff: myrows, + } + } +} + +#[repr(C)] +struct Buffer { + data: *mut u8, + len: usize, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct TringDevice<'a> { + index: u32, + name: RString<'a>, + unique_id: RString<'a>, + int_key: RString<'a>, +} + +impl<'a> TringDevice<'a> { + pub fn empty() -> Self { + let name = RString::from_string("empty".to_string()); + let unique_id = RString::from_string("empty".to_string()); + let int_key = RString::from_string("empty".to_string()); + Self { + index: 99, + name: name, + unique_id: unique_id, + int_key: int_key, + } + } + + pub fn from_audio_device(index: u32, src: AudioDevice) -> Self { + let src_name = RString::from_string(src.name); + let src_unique_id = RString::from_string(src.unique_id); + let src_int_key = RString::from_string(src.i18n_key); + Self { + index: index, + name: src_name, + unique_id: src_unique_id, + int_key: src_int_key, + } + } + pub fn from_fields( + index: u32, + src_name: String, + src_unique_id: String, + src_i18n_key: String, + ) -> Self { + let src_name = RString::from_string(src_name); + let src_unique_id = RString::from_string(src_unique_id); + let src_int_key = RString::from_string(src_i18n_key); + Self { + index: index, + name: src_name, + unique_id: src_unique_id, + int_key: src_int_key, + } + } +} diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index df7e51cb..134ed48a 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -57,6 +57,7 @@ pub mod protobuf; #[cfg(any(target_os = "android", feature = "check-all"))] /// Android specific implementation. +/// cbindgen:ignore mod android { #[macro_use] mod jni_util; @@ -75,6 +76,7 @@ mod android { #[cfg(any(target_os = "ios", feature = "check-all"))] /// iOS specific implementation. +/// cbindgen:ignore mod ios { mod api { pub mod call_manager_interface; @@ -91,6 +93,12 @@ pub mod electron; #[cfg(feature = "native")] pub mod native; +#[cfg(feature = "java")] +pub mod java { + mod java; + mod jtypes; +} + /// Foreign Function Interface (FFI) to WebRTC C++ library. pub mod webrtc { pub mod arc;