Live video on the AT Protocol

Merge pull request #725 from streamplace/eli/determinism-rebooted

determinism rebooted

authored by Eli Mallon and committed by GitHub 07320c22 65802423

+4013 -771
+97 -2
Cargo.lock
··· 615 ] 616 617 [[package]] 618 name = "camino" 619 version = "1.1.11" 620 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1429 version = "0.0.0" 1430 dependencies = [ 1431 "anyhow", 1432 - "c2pa", 1433 "schemars 0.8.22", 1434 "serde", 1435 "serde_json", ··· 2640 dependencies = [ 2641 "anyhow", 2642 "async-trait", 2643 "bytes", 2644 - "c2pa", 2645 "hex", 2646 "iroh", 2647 "iroh-base",
··· 615 ] 616 617 [[package]] 618 + name = "c2pa" 619 + version = "0.58.0" 620 + source = "git+https://github.com/streamplace/c2pa-rs.git?rev=544825abfaf9e588813e18dba70c8d5afd039d46#544825abfaf9e588813e18dba70c8d5afd039d46" 621 + dependencies = [ 622 + "asn1-rs", 623 + "async-generic", 624 + "async-recursion", 625 + "async-trait", 626 + "atree", 627 + "base64 0.22.1", 628 + "bcder", 629 + "byteorder", 630 + "byteordered", 631 + "bytes", 632 + "chrono", 633 + "ciborium", 634 + "config", 635 + "console_log", 636 + "const-hex", 637 + "const-oid 0.9.6", 638 + "conv", 639 + "coset", 640 + "der 0.7.10", 641 + "ed25519-dalek 2.2.0", 642 + "env_logger", 643 + "extfmt", 644 + "getrandom 0.2.16", 645 + "hex", 646 + "hex-literal", 647 + "http", 648 + "id3", 649 + "img-parts", 650 + "iref", 651 + "jfifdump", 652 + "js-sys", 653 + "lazy_static", 654 + "log", 655 + "memchr", 656 + "mp4", 657 + "nom", 658 + "non-empty-string", 659 + "nonempty-collections", 660 + "num-bigint-dig", 661 + "openssl", 662 + "pem", 663 + "pkcs1", 664 + "pkcs8 0.10.2", 665 + "png_pong", 666 + "quick-xml", 667 + "rand 0.8.5", 668 + "rand_chacha 0.3.1", 669 + "rand_core 0.9.3", 670 + "range-set", 671 + "rasn", 672 + "rasn-cms", 673 + "rasn-ocsp", 674 + "rasn-pkix", 675 + "regex", 676 + "reqwest", 677 + "riff", 678 + "ring", 679 + "rsa", 680 + "serde", 681 + "serde-transcode", 682 + "serde-wasm-bindgen", 683 + "serde_bytes", 684 + "serde_cbor", 685 + "serde_derive", 686 + "serde_json", 687 + "serde_with", 688 + "sha1", 689 + "sha2 0.10.9", 690 + "spki 0.7.3", 691 + "static-iref", 692 + "tempfile", 693 + "thiserror 2.0.16", 694 + "toml 0.8.23", 695 + "treeline", 696 + "ureq", 697 + "url", 698 + "uuid", 699 + "wasm-bindgen", 700 + "wasm-bindgen-futures", 701 + "web-sys", 702 + "web-time", 703 + "windows-core", 704 + "wstd", 705 + "x509-certificate", 706 + "x509-parser", 707 + "zeroize", 708 + "zip", 709 + ] 710 + 711 + [[package]] 712 name = "camino" 713 version = "1.1.11" 714 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1523 version = "0.0.0" 1524 dependencies = [ 1525 "anyhow", 1526 + "c2pa 0.58.0 (git+https://github.com/hyphacoop/c2pa-rs.git?rev=1b84d40219b27340a30fc309250e774e8a7b7761)", 1527 "schemars 0.8.22", 1528 "serde", 1529 "serde_json", ··· 2734 dependencies = [ 2735 "anyhow", 2736 "async-trait", 2737 + "base64 0.22.1", 2738 "bytes", 2739 + "c2pa 0.58.0 (git+https://github.com/streamplace/c2pa-rs.git?rev=544825abfaf9e588813e18dba70c8d5afd039d46)", 2740 "hex", 2741 "iroh", 2742 "iroh-base",
+2 -1
go.mod
··· 13 require ( 14 firebase.google.com/go/v4 v4.14.1 15 github.com/99designs/gqlgen v0.17.64 16 github.com/NYTimes/gziphandler v1.1.1 17 github.com/ThalesGroup/crypto11 v0.0.0-00010101000000-000000000000 18 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d ··· 65 go.opentelemetry.io/otel v1.36.0 66 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 67 go.opentelemetry.io/otel/sdk v1.36.0 68 - go.opentelemetry.io/otel/trace v1.36.0 69 go.uber.org/goleak v1.3.0 70 golang.org/x/image v0.30.0 71 golang.org/x/net v0.43.0 ··· 499 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect 500 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect 501 go.opentelemetry.io/otel/metric v1.36.0 // indirect 502 go.opentelemetry.io/proto/otlp v1.5.0 // indirect 503 go.uber.org/atomic v1.11.0 // indirect 504 go.uber.org/automaxprocs v1.6.0 // indirect
··· 13 require ( 14 firebase.google.com/go/v4 v4.14.1 15 github.com/99designs/gqlgen v0.17.64 16 + github.com/Eyevinn/mp4ff v0.50.0 17 github.com/NYTimes/gziphandler v1.1.1 18 github.com/ThalesGroup/crypto11 v0.0.0-00010101000000-000000000000 19 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d ··· 66 go.opentelemetry.io/otel v1.36.0 67 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 68 go.opentelemetry.io/otel/sdk v1.36.0 69 go.uber.org/goleak v1.3.0 70 golang.org/x/image v0.30.0 71 golang.org/x/net v0.43.0 ··· 499 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect 500 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect 501 go.opentelemetry.io/otel/metric v1.36.0 // indirect 502 + go.opentelemetry.io/otel/trace v1.36.0 // indirect 503 go.opentelemetry.io/proto/otlp v1.5.0 // indirect 504 go.uber.org/atomic v1.11.0 // indirect 505 go.uber.org/automaxprocs v1.6.0 // indirect
+4 -2
go.sum
··· 104 github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= 105 github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= 106 github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= 107 github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 h1:Sz1JIXEcSfhz7fUi7xHnhpIE0thVASYjvosApmHuD2k= 108 github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1/go.mod h1:n/LSCXNuIYqVfBlVXyHfMQkZDdp1/mmxfSjADd3z1Zg= 109 github.com/Kagami/go-avif v0.1.0 h1:8GHAGLxCdFfhpd4Zg8j1EqO7rtcQNenxIDerC/uu68w= ··· 463 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 464 github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 465 github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 466 - github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= 467 - github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= 468 github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4= 469 github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY= 470 github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
··· 104 github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= 105 github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= 106 github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= 107 + github.com/Eyevinn/mp4ff v0.50.0 h1:vFlsvpQh5Jfz++cuaeTI90vbID5dAabebvvN/l9lom0= 108 + github.com/Eyevinn/mp4ff v0.50.0/go.mod h1:hJNUUqOBryLAzUW9wpCJyw2HaI+TCd2rUPhafoS5lgg= 109 github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 h1:Sz1JIXEcSfhz7fUi7xHnhpIE0thVASYjvosApmHuD2k= 110 github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1/go.mod h1:n/LSCXNuIYqVfBlVXyHfMQkZDdp1/mmxfSjADd3z1Zg= 111 github.com/Kagami/go-avif v0.1.0 h1:8GHAGLxCdFfhpd4Zg8j1EqO7rtcQNenxIDerC/uu68w= ··· 465 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 466 github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 467 github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 468 + github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= 469 + github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= 470 github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4= 471 github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY= 472 github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
+121
hack/compare-hash.sh
···
··· 1 + #!/bin/bash 2 + 3 + set -euo pipefail 4 + 5 + ONE="$(realpath "$1")" 6 + TWO="$(realpath "$2")" 7 + BASE_ONE="$(basename "$ONE")" 8 + BASE_TWO="$(basename "$TWO")" 9 + 10 + if [[ -d "$ONE" && -d "$TWO" ]]; then 11 + FILES_ONE=$(find "$ONE" -maxdepth 1 -mindepth 1 -type f -name '*.mp4' | xargs -L 1 basename | sort) 12 + FILES_TWO=$(find "$TWO" -maxdepth 1 -mindepth 1 -type f -name '*.mp4' | xargs -L 1 basename | sort) 13 + 14 + NUM_FILES_ONE=$(echo "$FILES_ONE" | wc -l) 15 + NUM_FILES_TWO=$(echo "$FILES_TWO" | wc -l) 16 + 17 + if [[ "$NUM_FILES_ONE" -ne "$NUM_FILES_TWO" ]]; then 18 + echo "Directory contents differ: different number of files" 19 + comm -3 <(echo "$FILES_ONE") <(echo "$FILES_TWO") 20 + exit 1 21 + fi 22 + 23 + # Compare files by their order in the sorted lists, regardless of filenames 24 + paste <(echo "$FILES_ONE") <(echo "$FILES_TWO") | while read -r file_one file_two; do 25 + # skip if either file entry is empty (may only occur if line counts mismatched, but that's handled above) 26 + [ -n "$file_one" ] && [ -n "$file_two" ] || continue 27 + echo "Comparing $file_one <=> $file_two" 28 + set +e 29 + "$0" "$ONE/$file_one" "$TWO/$file_two" 30 + set -e 31 + done 32 + exit 0 33 + fi 34 + 35 + 36 + cd "$(mktemp -d)" 37 + HASH_ONE=$(openssl sha256 "$ONE" | awk '{print $2}') 38 + HASH_TWO=$(openssl sha256 "$TWO" | awk '{print $2}') 39 + if [ "$HASH_ONE" = "$HASH_TWO" ]; then 40 + echo "Identical: $BASE_ONE $BASE_TWO $(pwd)" 41 + exit 0 42 + fi 43 + pwd 44 + echo "Hash for $ONE: $HASH_ONE" 45 + echo "Hash for $TWO: $HASH_TWO" 46 + 47 + xxd "$ONE" > "1.xxd" 48 + xxd "$TWO" > "2.xxd" 49 + (diff --color=always "1.xxd" "2.xxd" || true) | head -n 5 50 + 51 + ffmpeg -y -loglevel fatal -i "$ONE" -c copy -f framemd5 "1.md5" 52 + ffmpeg -y -loglevel fatal -i "$TWO" -c copy -f framemd5 "2.md5" 53 + (diff --color=always "1.md5" "2.md5" || true) | head -n 5 54 + 55 + ffprobe -loglevel fatal -show_frames "$ONE" > "1.frames" 56 + ffprobe -loglevel fatal -show_frames "$TWO" > "2.frames" 57 + (diff --color=always "1.frames" "2.frames" || true) | head -n 5 58 + 59 + set +e 60 + echo -e "\033[0m" 61 + video_frames_one="$(cat 1.frames | grep media_type=video | wc -l | xargs)" 62 + video_frames_two="$(cat 2.frames | grep media_type=video | wc -l | xargs)" 63 + if [[ "$video_frames_one" -ne "$video_frames_two" ]]; then 64 + echo "Video frame count mismatch: $video_frames_one -> $video_frames_two" 65 + fi 66 + set -e 67 + 68 + audio_frames_one="$(cat 1.frames | grep media_type=audio | wc -l | xargs)" 69 + audio_frames_two="$(cat 2.frames | grep media_type=audio | wc -l | xargs)" 70 + if [[ "$audio_frames_one" -ne "$audio_frames_two" ]]; then 71 + echo "Audio frame count mismatch: $audio_frames_one -> $audio_frames_two" 72 + fi 73 + 74 + # ffmpeg -y -loglevel fatal -i "$ONE" -frames:v 1 -c copy -an 1frame.h264 75 + # ffmpeg -y -loglevel fatal -i "$TWO" -frames:v 1 -c copy -an 2frame.h264 76 + 77 + bash -c "ffmpeg -y -bsf:v trace_headers -i \"$ONE\" -c copy -f null /dev/null 2>&1" | sed 's/\[trace_headers @ 0x[0-9a-f]*\]//' > 1.trace_headers 78 + bash -c "ffmpeg -y -bsf:v trace_headers -i \"$TWO\" -c copy -f null /dev/null 2>&1" | sed 's/\[trace_headers @ 0x[0-9a-f]*\]//' > 2.trace_headers 79 + (diff --color=always "1.trace_headers" "2.trace_headers" || true) | head -n 5 80 + 81 + go install github.com/Eyevinn/mp4ff/cmd/mp4ff-info@latest || true 82 + mp4ff-info "$ONE" > 1.mp4ff-info 83 + mp4ff-info "$TWO" > 2.mp4ff-info 84 + (diff --color=always "1.mp4ff-info" "2.mp4ff-info" || true) | head -n 5 85 + 86 + # ffmpeg -y -loglevel fatal -i "$ONE" -c copy 1tweaked.mp4 87 + # ffmpeg -y -loglevel fatal -i "$TWO" -c copy 2tweaked.mp4 88 + 89 + # ffmpeg -y -bsf:v trace_headers -i 1tweaked.mp4 -c copy -f null /dev/null 2>&1 | sed 's/.*\]//' > 1.trace_headers 90 + # ffmpeg -y -bsf:v trace_headers -i 2tweaked.mp4 -c copy -f null /dev/null 2>&1 | sed 's/.*\]//' > 2.trace_headers 91 + 92 + # diff --color=always "1.trace_headers" "2.trace_headers" || true 93 + 94 + # ffmpeg -y -loglevel fatal -bsf:v h264_redundant_pps -i "$ONE" -c copy 1tweaked.mp4 95 + # ffmpeg -y -loglevel fatal -bsf:v h264_redundant_pps -i "$TWO" -c copy 2tweaked.mp4 96 + 97 + # ffmpeg -y -loglevel fatal -i 1tweaked.mp4 -c copy -f framemd5 "1tweaked.mp4.md5" 98 + # ffmpeg -y -loglevel fatal -i 2tweaked.mp4 -c copy -f framemd5 "2tweaked.mp4.md5" 99 + # (diff --color=always "1tweaked.mp4.md5" "2tweaked.mp4.md5" || true) 100 + 101 + # (diff --color=always "1.trace_headers" "2.trace_headers" || true) 102 + 103 + # ffmpeg -y -loglevel fatal -i "$ONE" -c:v copy -bsf:v h264_mp4toannexb -an 1.ts 104 + # ffmpeg -y -loglevel fatal -i "$TWO" -c:v copy -bsf:v h264_mp4toannexb -an 2.ts 105 + # ffmpeg -y -loglevel fatal -i 1.ts -c copy -f framemd5 "1.ts.md5" 106 + # ffmpeg -y -loglevel fatal -i 2.ts -c copy -f framemd5 "2.ts.md5" 107 + # (diff --color=always "1.ts.md5" "2.ts.md5" || true) 108 + 109 + echo -e "\033[0m" 110 + echo "Useful commands:" 111 + echo "Compare ffprobe -show_frames data:" 112 + echo " meld $(realpath 1.frames) $(realpath 2.frames)" 113 + echo "Compare frame headers:" 114 + echo " meld $(realpath 1.trace_headers) $(realpath 2.trace_headers)" 115 + echo "Compare hex hashes:" 116 + echo " meld $(realpath 1.xxd) $(realpath 2.xxd)" 117 + echo "Compare framemd5 hashes:" 118 + echo " meld $(realpath 1.md5) $(realpath 2.md5)" 119 + echo "Compare mp4ff-info data:" 120 + echo " meld $(realpath 1.mp4ff-info) $(realpath 2.mp4ff-info)" 121 + exit 1
+31
hack/compare-oneshot.sh
···
··· 1 + #!/bin/bash 2 + 3 + # Test that we can combine then split segments and get the same files 4 + 5 + set -euo pipefail 6 + 7 + SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 8 + 9 + # Detect platform/arch for correct build dir 10 + UNAME_S=$(uname -s | tr '[:upper:]' '[:lower:]') 11 + UNAME_M=$(uname -m) 12 + if [[ "$UNAME_S" == "darwin" && "$UNAME_M" == "arm64" ]]; then 13 + BUILD_DIR="build-darwin-arm64" 14 + elif [[ "$UNAME_S" == "linux" && "$UNAME_M" == "x86_64" ]]; then 15 + BUILD_DIR="build-linux-amd64" 16 + else 17 + echo "Unsupported platform: $UNAME_S/$UNAME_M" 18 + exit 1 19 + fi 20 + 21 + DEBUG_DIR="$(mktemp -d)" 22 + mkdir -p "$DEBUG_DIR/segments" 23 + set +e 24 + $SCRIPT_DIR/../$BUILD_DIR/streamplace combine --debug-dir="$DEBUG_DIR/segments-1" "$DEBUG_DIR/combined.mp4" $(find "$@" -name '*.mp4' | sort) 25 + EXIT_CODE=$? 26 + set -e 27 + if [ $EXIT_CODE -ne 0 ]; then 28 + $SCRIPT_DIR/compare-hash.sh "$@" "$DEBUG_DIR/segments-1" 29 + exit $EXIT_CODE 30 + fi 31 + echo "Success"
+31
hack/compare-roundtrip.sh
···
··· 1 + #!/bin/bash 2 + 3 + # Test that we can combine then split segments and get the same files 4 + 5 + set -euo pipefail 6 + 7 + SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 8 + 9 + # Detect platform/arch for correct build dir 10 + UNAME_S=$(uname -s | tr '[:upper:]' '[:lower:]') 11 + UNAME_M=$(uname -m) 12 + if [[ "$UNAME_S" == "darwin" && "$UNAME_M" == "arm64" ]]; then 13 + BUILD_DIR="build-darwin-arm64" 14 + elif [[ "$UNAME_S" == "linux" && "$UNAME_M" == "x86_64" ]]; then 15 + BUILD_DIR="build-linux-amd64" 16 + else 17 + echo "Unsupported platform: $UNAME_S/$UNAME_M" 18 + exit 1 19 + fi 20 + 21 + DEBUG_DIR="$(mktemp -d)" 22 + set +e 23 + $SCRIPT_DIR/../$BUILD_DIR/streamplace combine --debug-dir="$DEBUG_DIR/segments-1" "$DEBUG_DIR/combined.mp4" $(find "$@" -name '*.mp4' | sort) 24 + $SCRIPT_DIR/../$BUILD_DIR/streamplace combine --debug-dir="$DEBUG_DIR/segments-2" "$DEBUG_DIR/combined2.mp4" $(find "$DEBUG_DIR/segments-1" -name '*.mp4' | sort) 25 + EXIT_CODE=$? 26 + set -e 27 + if [ $EXIT_CODE -ne 0 ]; then 28 + $SCRIPT_DIR/compare-hash.sh "$DEBUG_DIR/segments-1" "$DEBUG_DIR/segments-2" 29 + exit $EXIT_CODE 30 + fi 31 + echo "Success"
+12
hack/deterministic-mux.sh
···
··· 1 + #!/bin/bash 2 + 3 + set -euo pipefail 4 + 5 + TMPDIR=$(mktemp -d) 6 + SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 7 + $SCRIPT_DIR/../build-linux-amd64/streamplace clip --out "$TMPDIR/combined1.mp4" "$@" 8 + sleep 2 9 + $SCRIPT_DIR/../build-linux-amd64/streamplace clip --out "$TMPDIR/combined2.mp4" "$@" 10 + xxd "$TMPDIR/combined1.mp4" > "$TMPDIR/combined1.mp4.xxd" 11 + xxd "$TMPDIR/combined2.mp4" > "$TMPDIR/combined2.mp4.xxd" 12 + diff --color=always "$TMPDIR/combined1.mp4.xxd" "$TMPDIR/combined2.mp4.xxd"
+1 -2
pkg/api/api.go
··· 87 mu sync.RWMutex 88 } 89 90 - func MakeStreamplaceAPI(cli *config.CLI, mod model.Model, statefulDB *statedb.StatefulDB, signer *eip712.EIP712Signer, noter notifications.FirebaseNotifier, mm *media.MediaManager, ms media.MediaSigner, bus *bus.Bus, atsync *atproto.ATProtoSynchronizer, d *director.Director, op *oatproxy.OATProxy) (*StreamplaceAPI, error) { 91 updater, err := PrepareUpdater(cli) 92 if err != nil { 93 return nil, err ··· 96 Model: mod, 97 StatefulDB: statefulDB, 98 Updater: updater, 99 - Signer: signer, 100 FirebaseNotifier: noter, 101 MediaManager: mm, 102 MediaSigner: ms,
··· 87 mu sync.RWMutex 88 } 89 90 + func MakeStreamplaceAPI(cli *config.CLI, mod model.Model, statefulDB *statedb.StatefulDB, noter notifications.FirebaseNotifier, mm *media.MediaManager, ms media.MediaSigner, bus *bus.Bus, atsync *atproto.ATProtoSynchronizer, d *director.Director, op *oatproxy.OATProxy) (*StreamplaceAPI, error) { 91 updater, err := PrepareUpdater(cli) 92 if err != nil { 93 return nil, err ··· 96 Model: mod, 97 StatefulDB: statefulDB, 98 Updater: updater, 99 FirebaseNotifier: noter, 100 MediaManager: mm, 101 MediaSigner: ms,
+1 -24
pkg/api/api_internal.go
··· 319 w.WriteHeader(204) 320 }) 321 322 - router.GET("/settings", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 323 - w.Header().Set("Access-Control-Allow-Origin", "*") 324 - w.Header().Set("Access-Control-Allow-Methods", "GET") 325 - w.Header().Set("Access-Control-Allow-Headers", "Content-Type") 326 - 327 - id := a.Signer.Hex() 328 - 329 - ident, err := a.Model.GetIdentity(id) 330 - if err != nil { 331 - errors.WriteHTTPInternalServerError(w, "unable to get settings", err) 332 - return 333 - } 334 - 335 - bs, err := json.Marshal(ident) 336 - if err != nil { 337 - errors.WriteHTTPInternalServerError(w, "unable to marshal json", err) 338 - return 339 - } 340 - if _, err := w.Write(bs); err != nil { 341 - log.Error(ctx, "error writing response", "error", err) 342 - } 343 - }) 344 - 345 router.GET("/followers/:user", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 346 user := p.ByName("user") 347 if user == "" { ··· 529 errors.WriteHTTPInternalServerError(w, "unable to create replay peer connection", err) 530 return 531 } 532 - answer, err := a.MediaManager.WebRTCIngest(ctx, &webrtc.SessionDescription{SDP: "placeholder"}, mediaSigner, pc, make(chan struct{})) 533 if err != nil { 534 errors.WriteHTTPInternalServerError(w, "unable to ingest web rtc", err) 535 return
··· 319 w.WriteHeader(204) 320 }) 321 322 router.GET("/followers/:user", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 323 user := p.ByName("user") 324 if user == "" { ··· 506 errors.WriteHTTPInternalServerError(w, "unable to create replay peer connection", err) 507 return 508 } 509 + answer, err := a.MediaManager.WebRTCIngest(ctx, &webrtc.SessionDescription{SDP: "placeholder"}, mediaSigner, pc, make(chan error, 1)) 510 if err != nil { 511 errors.WriteHTTPInternalServerError(w, "unable to ingest web rtc", err) 512 return
+1 -1
pkg/api/playback.go
··· 118 errors.WriteHTTPInternalServerError(w, "unable to create peer connection", err) 119 return 120 } 121 - answer, err := a.MediaManager.WebRTCIngest(ctx, &offer, mediaSigner, pc, make(chan struct{})) 122 if err != nil { 123 errors.WriteHTTPInternalServerError(w, "error playing back", err) 124 return
··· 118 errors.WriteHTTPInternalServerError(w, "unable to create peer connection", err) 119 return 120 } 121 + answer, err := a.MediaManager.WebRTCIngest(ctx, &offer, mediaSigner, pc, make(chan error, 1)) 122 if err != nil { 123 errors.WriteHTTPInternalServerError(w, "error playing back", err) 124 return
+1 -6
pkg/api/stream_key.go
··· 82 return nil, err 83 } 84 85 - var mediaSigner media.MediaSigner 86 - if a.CLI.ExternalSigning { 87 - mediaSigner, err = media.MakeMediaSignerExt(ctx, a.CLI, did, addrBytes, a.Model) 88 - } else { 89 - mediaSigner, err = media.MakeMediaSigner(ctx, a.CLI, did, signer, a.Model) 90 - } 91 if err != nil { 92 return nil, fmt.Errorf("invalid authorization key (not valid secp256k1): %w", err) 93 }
··· 82 return nil, err 83 } 84 85 + mediaSigner, err := media.MakeMediaSigner(ctx, a.CLI, did, signer, a.Model) 86 if err != nil { 87 return nil, fmt.Errorf("invalid authorization key (not valid secp256k1): %w", err) 88 }
+15 -1
pkg/aqio/aqio.go
··· 7 "github.com/johncgriffin/overflow" 8 ) 9 10 // ReadWriteSeeker is an in-memory io.ReadWriteSeeker implementation 11 type ReadWriteSeeker struct { 12 buf []byte ··· 14 } 15 16 // Write implements the io.Writer interface 17 func (rws *ReadWriteSeeker) Write(p []byte) (n int, err error) { 18 minCap := overflow.Addp(rws.pos, len(p)) 19 if minCap > cap(rws.buf) { // Make sure buf has enough capacity: 20 - buf2 := make([]byte, len(rws.buf), overflow.Addp(minCap, len(p))) // add some extra 21 copy(buf2, rws.buf) 22 rws.buf = buf2 23 }
··· 7 "github.com/johncgriffin/overflow" 8 ) 9 10 + func NewReadWriteSeeker(buf []byte) *ReadWriteSeeker { 11 + return &ReadWriteSeeker{buf: buf, pos: 0} 12 + } 13 + 14 // ReadWriteSeeker is an in-memory io.ReadWriteSeeker implementation 15 type ReadWriteSeeker struct { 16 buf []byte ··· 18 } 19 20 // Write implements the io.Writer interface 21 + // TODO: This would probably be better as a linked list for writing; the read 22 + // requirements are minimal so doing a bit more math is okay 23 func (rws *ReadWriteSeeker) Write(p []byte) (n int, err error) { 24 + // fmt.Printf("Write: pos=%d len(p)=%d\n", rws.pos, len(p)) 25 minCap := overflow.Addp(rws.pos, len(p)) 26 if minCap > cap(rws.buf) { // Make sure buf has enough capacity: 27 + // fmt.Printf("Write: pos=%d len(p)=%d minCap=%d\n", rws.pos, len(p), minCap) 28 + newCap := cap(rws.buf) * 2 29 + if newCap == 0 { 30 + newCap = 128 31 + } else if newCap < minCap { 32 + newCap = minCap * 2 33 + } 34 + buf2 := make([]byte, len(rws.buf), newCap) // double 35 copy(buf2, rws.buf) 36 rws.buf = buf2 37 }
+153
pkg/c2patypes/stream_adapter.go
···
··· 1 + package c2patypes 2 + 3 + import ( 4 + "errors" 5 + "fmt" 6 + "io" 7 + 8 + "stream.place/streamplace/pkg/iroh/generated/iroh_streamplace" 9 + ) 10 + 11 + // type Stream interface { 12 + // // Read a stream of bytes from the stream 13 + // ReadStream(length uint64) ([]byte, error) 14 + // // Seek to a position in the stream 15 + // SeekStream(pos int64, mode uint64) (uint64, error) 16 + // // Write a stream of bytes to the stream 17 + // WriteStream(data []byte) (uint64, error) 18 + // } 19 + 20 + // pub enum SeekMode { 21 + // Start = 0, 22 + // End = 1, 23 + // Current = 2, 24 + // } 25 + 26 + const ( 27 + SeekModeStart uint64 = 0 28 + SeekModeEnd uint64 = 1 29 + SeekModeCurrent uint64 = 2 30 + ) 31 + 32 + func NewReader(rs io.ReadSeeker) *C2PAStreamReader { 33 + return &C2PAStreamReader{ReadSeeker: rs} 34 + } 35 + 36 + func NewWriter(rws io.ReadWriteSeeker) *C2PAStreamWriter { 37 + return &C2PAStreamWriter{ReadWriteSeeker: rws} 38 + } 39 + 40 + // Wrapped io.ReadSeeker for passing to Rust. Doesn't write. 41 + type C2PAStreamReader struct { 42 + io.ReadSeeker 43 + } 44 + 45 + func (s *C2PAStreamReader) ReadStream(length uint64) ([]byte, error) { 46 + return readStream(s.ReadSeeker, length) 47 + } 48 + 49 + func (s *C2PAStreamReader) SeekStream(pos int64, mode uint64) (uint64, error) { 50 + return seekStream(s.ReadSeeker, pos, mode) 51 + } 52 + 53 + func (s *C2PAStreamReader) WriteStream(data []byte) (uint64, error) { 54 + return 0, fmt.Errorf("Writing is not implemented for C2PAStreamReader") 55 + } 56 + 57 + // Wrapped io.Writer for passing to Rust. 58 + type C2PAStreamWriter struct { 59 + io.ReadWriteSeeker 60 + } 61 + 62 + func (s *C2PAStreamWriter) ReadStream(length uint64) ([]byte, error) { 63 + return readStream(s.ReadWriteSeeker, length) 64 + } 65 + 66 + func (s *C2PAStreamWriter) SeekStream(pos int64, mode uint64) (uint64, error) { 67 + return seekStream(s.ReadWriteSeeker, pos, mode) 68 + } 69 + 70 + func (s *C2PAStreamWriter) WriteStream(data []byte) (uint64, error) { 71 + return writeStream(s.ReadWriteSeeker, data) 72 + } 73 + 74 + func readStream(r io.ReadSeeker, length uint64) ([]byte, error) { 75 + // fmt.Printf("read length=%d\n", length) 76 + bs := make([]byte, length) 77 + read, err := r.Read(bs) 78 + if err != nil { 79 + if errors.Is(err, io.EOF) { 80 + if read == 0 { 81 + // fmt.Printf("read EOF read=%d returning empty?", read) 82 + return []byte{}, nil 83 + } 84 + // partial := bs[read:] 85 + // return partial, nil 86 + } 87 + // fmt.Printf("io error=%s\n", err) 88 + return []byte{}, err 89 + } 90 + if uint64(read) < length { 91 + partial := bs[:read] 92 + // fmt.Printf("read returning partial read=%d len=%d\n", read, len(partial)) 93 + return partial, nil 94 + } 95 + // fmt.Printf("read returning full read=%d len=%d\n", read, len(bs)) 96 + return bs, nil 97 + } 98 + 99 + func seekStream(r io.ReadSeeker, pos int64, mode uint64) (uint64, error) { 100 + // fmt.Printf("seek pos=%d\n", pos) 101 + var seekMode int 102 + if mode == SeekModeCurrent { 103 + seekMode = io.SeekCurrent 104 + } else if mode == SeekModeStart { 105 + seekMode = io.SeekStart 106 + } else if mode == SeekModeEnd { 107 + seekMode = io.SeekEnd 108 + } else { 109 + // fmt.Printf("seek mode unsupported mode=%d\n", mode) 110 + return 0, fmt.Errorf("unknown seek mode: %d", mode) 111 + } 112 + newPos, err := r.Seek(pos, seekMode) 113 + if err != nil { 114 + return 0, err 115 + } 116 + return uint64(newPos), nil 117 + } 118 + 119 + func writeStream(w io.ReadWriteSeeker, data []byte) (uint64, error) { 120 + wrote, err := w.Write(data) 121 + if err != nil { 122 + return uint64(wrote), err 123 + } 124 + return uint64(wrote), nil 125 + } 126 + 127 + type ManyStreams struct { 128 + Streams []io.ReadSeeker 129 + index int 130 + } 131 + 132 + func NewManyStreams() *ManyStreams { 133 + return &ManyStreams{Streams: []io.ReadSeeker{}, index: 0} 134 + } 135 + 136 + func (m *ManyStreams) AddStream(stream io.ReadSeeker) { 137 + m.Streams = append(m.Streams, stream) 138 + } 139 + 140 + func (m *ManyStreams) Next() *iroh_streamplace.Stream { 141 + if m.index >= len(m.Streams) { 142 + return nil 143 + } 144 + stream := m.Streams[m.index] 145 + m.index++ 146 + var r iroh_streamplace.Stream 147 + if rws, ok := stream.(io.ReadWriteSeeker); ok { 148 + r = NewWriter(rws) 149 + } else { 150 + r = NewReader(stream) 151 + } 152 + return &r 153 + }
-29
pkg/cmd/clip.go
··· 1 - package cmd 2 - 3 - import ( 4 - "context" 5 - "fmt" 6 - "os" 7 - 8 - "stream.place/streamplace/pkg/gstinit" 9 - "stream.place/streamplace/pkg/log" 10 - "stream.place/streamplace/pkg/media" 11 - ) 12 - 13 - func Clip(ctx context.Context, args []string, out string) error { 14 - if out == "" { 15 - return fmt.Errorf("out is required") 16 - } 17 - log.Log(ctx, "clip", "out", out) 18 - gstinit.InitGST() 19 - fd, err := os.Create(out) 20 - if err != nil { 21 - return err 22 - } 23 - defer fd.Close() 24 - err = media.Clip(ctx, args, fd) 25 - if err != nil { 26 - return err 27 - } 28 - return nil 29 - }
···
+92
pkg/cmd/combine.go
···
··· 1 + package cmd 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "io" 7 + "os" 8 + "path/filepath" 9 + 10 + "stream.place/streamplace/pkg/aqio" 11 + "stream.place/streamplace/pkg/config" 12 + "stream.place/streamplace/pkg/gstinit" 13 + "stream.place/streamplace/pkg/log" 14 + "stream.place/streamplace/pkg/media" 15 + ) 16 + 17 + func Combine(ctx context.Context, build *config.BuildFlags, allArgs []string) error { 18 + gstinit.InitGST() 19 + cli := &config.CLI{Build: build} 20 + fs := cli.NewFlagSet("streamplace combine") 21 + debugDir := fs.String("debug-dir", "", "directory to write debug files to") 22 + 23 + err := cli.Parse(fs, allArgs) 24 + if err != nil { 25 + return err 26 + } 27 + if *debugDir != "" { 28 + err := os.MkdirAll(*debugDir, 0755) 29 + if err != nil { 30 + return fmt.Errorf("failed to create debug directory: %w", err) 31 + } 32 + } 33 + log.Debug(context.Background(), "combine command: starting", "args", fs.Args()) 34 + ctx = log.WithDebugValue(ctx, cli.Debug) 35 + cryptoSigner, err := createSigner(ctx, cli) 36 + if err != nil { 37 + return err 38 + } 39 + ms, err := media.MakeMediaSigner(ctx, cli, "combine", cryptoSigner, nil) 40 + if err != nil { 41 + return err 42 + } 43 + args := fs.Args() 44 + outFile := args[0] 45 + inputs := args[1:] 46 + log.Log(ctx, "combining segments", "outFile", outFile, "inputs", inputs) 47 + outFd, err := os.Create(outFile) 48 + if err != nil { 49 + return err 50 + } 51 + defer outFd.Close() 52 + inputFds := make([]io.ReadSeeker, len(inputs)) 53 + for i, input := range inputs { 54 + fd, err := os.Open(input) 55 + if err != nil { 56 + return err 57 + } 58 + defer fd.Close() 59 + inputFds[i] = fd 60 + } 61 + err = media.CombineSegments(ctx, inputFds, ms, outFd) 62 + if err != nil { 63 + return err 64 + } 65 + err = CheckCombined(ctx, cli, outFd, *debugDir) 66 + if err != nil { 67 + return err 68 + } 69 + return nil 70 + } 71 + 72 + func CheckCombined(ctx context.Context, cli *config.CLI, inFD io.ReadWriteSeeker, debugDir string) error { 73 + _, err := inFD.Seek(0, io.SeekStart) 74 + if err != nil { 75 + return err 76 + } 77 + err = media.SplitSegments(ctx, cli, inFD, func(fname string) media.ReadWriteSeekCloser { 78 + if debugDir == "" { 79 + return aqio.NewReadWriteSeeker([]byte{}) 80 + } 81 + fd, err := os.Create(filepath.Join(debugDir, fname)) 82 + if err != nil { 83 + panic(fmt.Errorf("failed to create debug file: %w", err)) 84 + } 85 + log.Log(ctx, "created debug file", "path", filepath.Join(debugDir, fname)) 86 + return fd 87 + }) 88 + if err != nil { 89 + return err 90 + } 91 + return nil 92 + }
+106
pkg/cmd/signer.go
···
··· 1 + package cmd 2 + 3 + import ( 4 + "context" 5 + "crypto" 6 + "fmt" 7 + "os" 8 + "strconv" 9 + 10 + "github.com/ThalesGroup/crypto11" 11 + "golang.org/x/term" 12 + "stream.place/streamplace/pkg/config" 13 + "stream.place/streamplace/pkg/crypto/signers" 14 + v0 "stream.place/streamplace/pkg/schema/v0" 15 + 16 + "stream.place/streamplace/pkg/crypto/signers/eip712" 17 + "stream.place/streamplace/pkg/log" 18 + ) 19 + 20 + func createSigner(ctx context.Context, cli *config.CLI) (crypto.Signer, error) { 21 + schema, err := v0.MakeV0Schema() 22 + if err != nil { 23 + return nil, err 24 + } 25 + eip712signer, err := eip712.MakeEIP712Signer(ctx, &eip712.EIP712SignerOptions{ 26 + Schema: schema, 27 + EthKeystorePath: cli.EthKeystorePath, 28 + EthAccountAddr: cli.EthAccountAddr, 29 + EthKeystorePassword: cli.EthPassword, 30 + }) 31 + if err != nil { 32 + return nil, err 33 + } 34 + var signer crypto.Signer = eip712signer 35 + if cli.PKCS11ModulePath != "" { 36 + conf := &crypto11.Config{ 37 + Path: cli.PKCS11ModulePath, 38 + } 39 + count := 0 40 + for _, val := range []string{cli.PKCS11TokenSlot, cli.PKCS11TokenLabel, cli.PKCS11TokenSerial} { 41 + if val != "" { 42 + count += 1 43 + } 44 + } 45 + if count != 1 { 46 + return nil, fmt.Errorf("need exactly one of pkcs11-token-slot, pkcs11-token-label, or pkcs11-token-serial (got %d)", count) 47 + } 48 + if cli.PKCS11TokenSlot != "" { 49 + num, err := strconv.ParseInt(cli.PKCS11TokenSlot, 10, 16) 50 + if err != nil { 51 + return nil, fmt.Errorf("error parsing pkcs11-slot: %w", err) 52 + } 53 + numint := int(num) 54 + // why does crypto11 want this as a reference? odd. 55 + conf.SlotNumber = &numint 56 + } 57 + if cli.PKCS11TokenLabel != "" { 58 + conf.TokenLabel = cli.PKCS11TokenLabel 59 + } 60 + if cli.PKCS11TokenSerial != "" { 61 + conf.TokenSerial = cli.PKCS11TokenSerial 62 + } 63 + pin := cli.PKCS11Pin 64 + if pin == "" { 65 + fmt.Printf("Please enter PKCS11 PIN: ") 66 + password, err := term.ReadPassword(int(os.Stdin.Fd())) 67 + fmt.Println("") 68 + if err != nil { 69 + return nil, fmt.Errorf("error reading PKCS11 password: %w", err) 70 + } 71 + pin = string(password) 72 + } 73 + conf.Pin = pin 74 + 75 + sc, err := crypto11.Configure(conf) 76 + if err != nil { 77 + return nil, fmt.Errorf("error initalizing PKCS11 HSM: %w", err) 78 + } 79 + var id []byte = nil 80 + var label []byte = nil 81 + if cli.PKCS11KeypairID != "" { 82 + num, err := strconv.ParseInt(cli.PKCS11KeypairID, 10, 8) 83 + if err != nil { 84 + return nil, fmt.Errorf("error parsing pkcs11-keypair-id: %w", err) 85 + } 86 + id = []byte{byte(num)} 87 + } 88 + if cli.PKCS11KeypairLabel != "" { 89 + label = []byte(cli.PKCS11KeypairLabel) 90 + } 91 + hwsigner, err := sc.FindKeyPair(id, label) 92 + if err != nil { 93 + return nil, fmt.Errorf("error finding keypair on PKCS11 token: %w", err) 94 + } 95 + if hwsigner == nil { 96 + return nil, fmt.Errorf("keypair on token not found (tried id='%s' label='%s')", cli.PKCS11KeypairID, cli.PKCS11KeypairLabel) 97 + } 98 + addr, err := signers.HexAddrFromSigner(hwsigner) 99 + if err != nil { 100 + return nil, fmt.Errorf("error getting ethereum address for hardware keypair: %w", err) 101 + } 102 + log.Log(ctx, "successfully initialized hardware signer", "address", addr) 103 + signer = hwsigner 104 + } 105 + return signer, nil 106 + }
+61
pkg/cmd/split.go
···
··· 1 + package cmd 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "io" 7 + "os" 8 + "path/filepath" 9 + 10 + "stream.place/streamplace/pkg/config" 11 + "stream.place/streamplace/pkg/log" 12 + "stream.place/streamplace/pkg/media" 13 + ) 14 + 15 + func Split(ctx context.Context, inFile, outDir string) error { 16 + inFD, err := os.Open(inFile) 17 + if err != nil { 18 + return err 19 + } 20 + defer inFD.Close() 21 + err = os.MkdirAll(outDir, 0755) 22 + if err != nil { 23 + return err 24 + } 25 + 26 + names := []string{} 27 + 28 + err = media.SplitSegments(ctx, &config.CLI{}, inFD, func(fname string) media.ReadWriteSeekCloser { 29 + fullPath := filepath.Join(outDir, fname) 30 + names = append(names, fullPath) 31 + log.Log(ctx, "creating segment file", "path", fullPath) 32 + fd, err := os.Create(fullPath) 33 + if err != nil { 34 + log.Error(ctx, "failed to open segment file", "error", err) 35 + return nil 36 + } 37 + return fd 38 + }) 39 + if err != nil { 40 + return fmt.Errorf("failed to split segments: %w", err) 41 + } 42 + 43 + for _, name := range names { 44 + fd, err := os.Open(name) 45 + if err != nil { 46 + return fmt.Errorf("failed to open segment file: %w", err) 47 + } 48 + defer fd.Close() 49 + bs, err := io.ReadAll(fd) 50 + if err != nil { 51 + return fmt.Errorf("failed to read segment file: %w", err) 52 + } 53 + _, err = media.ValidateMP4Media(ctx, bs) 54 + if err != nil { 55 + return fmt.Errorf("failed to validate segment file: %w", err) 56 + } 57 + log.Log(ctx, "validated segment file", "path", name) 58 + } 59 + 60 + return nil 61 + }
+21 -111
pkg/cmd/streamplace.go
··· 3 import ( 4 "bytes" 5 "context" 6 - "crypto" 7 "crypto/rand" 8 "errors" 9 "flag" ··· 11 "net/url" 12 "os" 13 "os/signal" 14 - "path/filepath" 15 "runtime" 16 "runtime/pprof" 17 "slices" ··· 25 "github.com/livepeer/go-livepeer/cmd/livepeer/starter" 26 "github.com/peterbourgon/ff/v3" 27 "github.com/streamplace/oatproxy/pkg/oatproxy" 28 - "golang.org/x/term" 29 "stream.place/streamplace/pkg/aqhttp" 30 "stream.place/streamplace/pkg/atproto" 31 "stream.place/streamplace/pkg/bus" 32 - "stream.place/streamplace/pkg/crypto/signers" 33 - "stream.place/streamplace/pkg/crypto/signers/eip712" 34 "stream.place/streamplace/pkg/director" 35 "stream.place/streamplace/pkg/iroh/generated/iroh_streamplace" 36 "stream.place/streamplace/pkg/log" 37 "stream.place/streamplace/pkg/media" ··· 40 "stream.place/streamplace/pkg/replication/iroh_replicator" 41 "stream.place/streamplace/pkg/replication/websocketrep" 42 "stream.place/streamplace/pkg/rtmps" 43 - v0 "stream.place/streamplace/pkg/schema/v0" 44 "stream.place/streamplace/pkg/spmetrics" 45 "stream.place/streamplace/pkg/statedb" 46 "stream.place/streamplace/pkg/storage" 47 48 - "github.com/ThalesGroup/crypto11" 49 _ "github.com/go-gst/go-glib/glib" 50 _ "github.com/go-gst/go-gst/gst" 51 "stream.place/streamplace/pkg/api" ··· 127 return WHIP(os.Args[2:]) 128 } 129 130 - if len(os.Args) > 1 && os.Args[1] == "clip" { 131 cli := config.CLI{Build: build} 132 - fs := cli.NewFlagSet("streamplace clip") 133 - out := fs.String("out", "", "output file") 134 135 err := cli.Parse(fs, os.Args[2:]) 136 if err != nil { ··· 138 } 139 ctx := context.Background() 140 ctx = log.WithDebugValue(ctx, cli.Debug) 141 - return Clip(ctx, fs.Args(), *out) 142 } 143 144 if len(os.Args) > 1 && os.Args[1] == "self-test" { ··· 203 if *version { 204 return nil 205 } 206 207 if len(os.Args) > 1 && os.Args[1] == "migrate" { 208 return statedb.Migrate(&cli) ··· 230 if err != nil { 231 return fmt.Errorf("error creating streamplace dir at %s:%w", cli.DataDir, err) 232 } 233 - schema, err := v0.MakeV0Schema() 234 - if err != nil { 235 - return err 236 - } 237 - eip712signer, err := eip712.MakeEIP712Signer(ctx, &eip712.EIP712SignerOptions{ 238 - Schema: schema, 239 - EthKeystorePath: cli.EthKeystorePath, 240 - EthAccountAddr: cli.EthAccountAddr, 241 - EthKeystorePassword: cli.EthPassword, 242 - }) 243 - if err != nil { 244 - return err 245 - } 246 - var signer crypto.Signer = eip712signer 247 - if cli.PKCS11ModulePath != "" { 248 - conf := &crypto11.Config{ 249 - Path: cli.PKCS11ModulePath, 250 - } 251 - count := 0 252 - for _, val := range []string{cli.PKCS11TokenSlot, cli.PKCS11TokenLabel, cli.PKCS11TokenSerial} { 253 - if val != "" { 254 - count += 1 255 - } 256 - } 257 - if count != 1 { 258 - return fmt.Errorf("need exactly one of pkcs11-token-slot, pkcs11-token-label, or pkcs11-token-serial (got %d)", count) 259 - } 260 - if cli.PKCS11TokenSlot != "" { 261 - num, err := strconv.ParseInt(cli.PKCS11TokenSlot, 10, 16) 262 - if err != nil { 263 - return fmt.Errorf("error parsing pkcs11-slot: %w", err) 264 - } 265 - numint := int(num) 266 - // why does crypto11 want this as a reference? odd. 267 - conf.SlotNumber = &numint 268 - } 269 - if cli.PKCS11TokenLabel != "" { 270 - conf.TokenLabel = cli.PKCS11TokenLabel 271 - } 272 - if cli.PKCS11TokenSerial != "" { 273 - conf.TokenSerial = cli.PKCS11TokenSerial 274 - } 275 - pin := cli.PKCS11Pin 276 - if pin == "" { 277 - fmt.Printf("Please enter PKCS11 PIN: ") 278 - password, err := term.ReadPassword(int(os.Stdin.Fd())) 279 - fmt.Println("") 280 - if err != nil { 281 - return fmt.Errorf("error reading PKCS11 password: %w", err) 282 - } 283 - pin = string(password) 284 - } 285 - conf.Pin = pin 286 - 287 - sc, err := crypto11.Configure(conf) 288 - if err != nil { 289 - return fmt.Errorf("error initalizing PKCS11 HSM: %w", err) 290 - } 291 - var id []byte = nil 292 - var label []byte = nil 293 - if cli.PKCS11KeypairID != "" { 294 - num, err := strconv.ParseInt(cli.PKCS11KeypairID, 10, 8) 295 - if err != nil { 296 - return fmt.Errorf("error parsing pkcs11-keypair-id: %w", err) 297 - } 298 - id = []byte{byte(num)} 299 - } 300 - if cli.PKCS11KeypairLabel != "" { 301 - label = []byte(cli.PKCS11KeypairLabel) 302 - } 303 - hwsigner, err := sc.FindKeyPair(id, label) 304 - if err != nil { 305 - return fmt.Errorf("error finding keypair on PKCS11 token: %w", err) 306 - } 307 - if hwsigner == nil { 308 - return fmt.Errorf("keypair on token not found (tried id='%s' label='%s')", cli.PKCS11KeypairID, cli.PKCS11KeypairLabel) 309 - } 310 - addr, err := signers.HexAddrFromSigner(hwsigner) 311 - if err != nil { 312 - return fmt.Errorf("error getting ethereum address for hardware keypair: %w", err) 313 - } 314 - log.Log(ctx, "successfully initialized hardware signer", "address", addr) 315 - signer = hwsigner 316 - } 317 318 mod, err := model.MakeDB(cli.DataFilePath([]string{"index"})) 319 if err != nil { ··· 459 Public: cli.PublicOAuth, 460 }) 461 d := director.NewDirector(mm, mod, &cli, b, op, state, replicator) 462 - a, err := api.MakeStreamplaceAPI(&cli, mod, state, eip712signer, noter, mm, ms, b, atsync, d, op) 463 if err != nil { 464 return err 465 } ··· 557 }) 558 559 if cli.TestStream { 560 - // regular stream self-test 561 - testSigner, err := eip712.MakeEIP712Signer(ctx, &eip712.EIP712SignerOptions{ 562 - Schema: schema, 563 - EthKeystorePath: filepath.Join(cli.DataDir, "test-signer"), 564 - }) 565 - if err != nil { 566 - return err 567 - } 568 atkey, err := atproto.ParsePubKey(signer.Public()) 569 if err != nil { 570 return err 571 } 572 did := atkey.DIDKey() 573 - testMediaSigner, err := media.MakeMediaSigner(ctx, &cli, did, testSigner, mod) 574 if err != nil { 575 return err 576 } ··· 589 }) 590 591 // Start a test stream that will run intermittently 592 - intermittentSigner, err := eip712.MakeEIP712Signer(ctx, &eip712.EIP712SignerOptions{ 593 - Schema: schema, 594 - EthKeystorePath: filepath.Join(cli.DataDir, "intermittent-signer"), 595 - }) 596 if err != nil { 597 return err 598 } 599 - atkey2, err := atproto.ParsePubKey(intermittentSigner.Public()) 600 if err != nil { 601 return err 602 } 603 did2 := atkey2.DIDKey() 604 - intermittentMediaSigner, err := media.MakeMediaSigner(ctx, &cli, did2, intermittentSigner, mod) 605 if err != nil { 606 return err 607 }
··· 3 import ( 4 "bytes" 5 "context" 6 "crypto/rand" 7 "errors" 8 "flag" ··· 10 "net/url" 11 "os" 12 "os/signal" 13 "runtime" 14 "runtime/pprof" 15 "slices" ··· 23 "github.com/livepeer/go-livepeer/cmd/livepeer/starter" 24 "github.com/peterbourgon/ff/v3" 25 "github.com/streamplace/oatproxy/pkg/oatproxy" 26 "stream.place/streamplace/pkg/aqhttp" 27 "stream.place/streamplace/pkg/atproto" 28 "stream.place/streamplace/pkg/bus" 29 "stream.place/streamplace/pkg/director" 30 + "stream.place/streamplace/pkg/gstinit" 31 "stream.place/streamplace/pkg/iroh/generated/iroh_streamplace" 32 "stream.place/streamplace/pkg/log" 33 "stream.place/streamplace/pkg/media" ··· 36 "stream.place/streamplace/pkg/replication/iroh_replicator" 37 "stream.place/streamplace/pkg/replication/websocketrep" 38 "stream.place/streamplace/pkg/rtmps" 39 "stream.place/streamplace/pkg/spmetrics" 40 "stream.place/streamplace/pkg/statedb" 41 "stream.place/streamplace/pkg/storage" 42 43 _ "github.com/go-gst/go-glib/glib" 44 _ "github.com/go-gst/go-gst/gst" 45 "stream.place/streamplace/pkg/api" ··· 121 return WHIP(os.Args[2:]) 122 } 123 124 + if len(os.Args) > 1 && os.Args[1] == "combine" { 125 + return Combine(context.Background(), build, os.Args[2:]) 126 + } 127 + 128 + if len(os.Args) > 1 && os.Args[1] == "split" { 129 cli := config.CLI{Build: build} 130 + fs := cli.NewFlagSet("streamplace split") 131 132 err := cli.Parse(fs, os.Args[2:]) 133 if err != nil { ··· 135 } 136 ctx := context.Background() 137 ctx = log.WithDebugValue(ctx, cli.Debug) 138 + if len(fs.Args()) != 2 { 139 + fmt.Println("usage: streamplace split [flags] [input file] [output directory]") 140 + os.Exit(1) 141 + } 142 + gstinit.InitGST() 143 + return Split(ctx, fs.Args()[0], fs.Args()[1]) 144 } 145 146 if len(os.Args) > 1 && os.Args[1] == "self-test" { ··· 205 if *version { 206 return nil 207 } 208 + signer, err := createSigner(ctx, &cli) 209 + if err != nil { 210 + return err 211 + } 212 213 if len(os.Args) > 1 && os.Args[1] == "migrate" { 214 return statedb.Migrate(&cli) ··· 236 if err != nil { 237 return fmt.Errorf("error creating streamplace dir at %s:%w", cli.DataDir, err) 238 } 239 240 mod, err := model.MakeDB(cli.DataFilePath([]string{"index"})) 241 if err != nil { ··· 381 Public: cli.PublicOAuth, 382 }) 383 d := director.NewDirector(mm, mod, &cli, b, op, state, replicator) 384 + a, err := api.MakeStreamplaceAPI(&cli, mod, state, noter, mm, ms, b, atsync, d, op) 385 if err != nil { 386 return err 387 } ··· 479 }) 480 481 if cli.TestStream { 482 atkey, err := atproto.ParsePubKey(signer.Public()) 483 if err != nil { 484 return err 485 } 486 did := atkey.DIDKey() 487 + testMediaSigner, err := media.MakeMediaSigner(ctx, &cli, did, signer, mod) 488 if err != nil { 489 return err 490 } ··· 503 }) 504 505 // Start a test stream that will run intermittently 506 if err != nil { 507 return err 508 } 509 + atkey2, err := atproto.ParsePubKey(signer.Public()) 510 if err != nil { 511 return err 512 } 513 did2 := atkey2.DIDKey() 514 + intermittentMediaSigner, err := media.MakeMediaSigner(ctx, &cli, did2, signer, mod) 515 if err != nil { 516 return err 517 }
+32 -2
pkg/config/config.go
··· 134 Replicators []string 135 WebsocketURL string 136 BehindHTTPSProxy bool 137 } 138 139 // ContentFilters represents the content filtering configuration ··· 195 fs.BoolVar(&cli.PrintChat, "print-chat", false, "print chat messages to stdout") 196 fs.StringVar(&cli.WHIPTest, "whip-test", "", "run a WHIP self-test with the given parameters") 197 fs.StringVar(&cli.RelayHost, "relay-host", "wss://bsky.network", "websocket url for relay firehose") 198 - fs.Bool("insecure", false, "DEPRECATED, does nothing.") 199 fs.StringVar(&cli.Color, "color", "", "'true' to enable colorized logging, 'false' to disable") 200 fs.StringVar(&cli.BroadcasterHost, "broadcaster-host", "", "public host for the broadcaster group that this node is a part of (excluding https:// e.g. stream.place)") 201 fs.StringVar(&cli.XXDeprecatedPublicHost, "public-host", "", "deprecated, use broadcaster-host or server-host instead as appropriate") 202 fs.StringVar(&cli.ServerHost, "server-host", "", "public host for this particular physical streamplace node. defaults to broadcaster-host and only must be set for multi-node broadcasters") 203 fs.BoolVar(&cli.Thumbnail, "thumbnail", true, "enable thumbnail generation") 204 fs.BoolVar(&cli.SmearAudio, "smear-audio", false, "enable audio smearing to create 'perfect' segment timestamps") 205 - fs.BoolVar(&cli.ExternalSigning, "external-signing", false, "enable external signing via exec (prevents potential memory leak)") 206 fs.StringVar(&cli.TracingEndpoint, "tracing-endpoint", "", "gRPC endpoint to send traces to") 207 fs.IntVar(&cli.RateLimitPerSecond, "rate-limit-per-second", 0, "rate limit for requests per second per ip") 208 fs.IntVar(&cli.RateLimitBurst, "rate-limit-burst", 0, "rate limit burst for requests per ip") ··· 221 fs.BoolVar(&cli.SQLLogging, "sql-logging", false, "enable sql logging") 222 fs.StringVar(&cli.SentryDSN, "sentry-dsn", "", "sentry dsn for error reporting") 223 fs.BoolVar(&cli.LivepeerDebug, "livepeer-debug", false, "log livepeer segments to $SP_DATA_DIR/livepeer-debug") 224 cli.StringSliceFlag(fs, &cli.Tickets, "tickets", []string{}, "tickets to join the swarm with") 225 fs.StringVar(&cli.IrohTopic, "iroh-topic", "", "topic to use for the iroh swarm (must be 32 bytes in hex)") 226 fs.BoolVar(&cli.DisableIrohRelay, "disable-iroh-relay", false, "disable the iroh relay") ··· 229 cli.StringSliceFlag(fs, &cli.Replicators, "replicators", []string{ReplicatorWebsocket}, "list of replication protocols to use (http, iroh)") 230 fs.StringVar(&cli.WebsocketURL, "websocket-url", "", "override the websocket (ws:// or wss://) url to use for replication (normally not necessary, used for testing)") 231 fs.BoolVar(&cli.BehindHTTPSProxy, "behind-https-proxy", false, "set to true if this node is behind an https proxy and we should report https URLs even though the node isn't serving HTTPS") 232 233 lpFlags := flag.NewFlagSet("livepeer", flag.ContinueOnError) 234 _ = starter.NewLivepeerConfig(lpFlags) ··· 644 func (cli *CLI) HasHTTPS() bool { 645 return cli.Secure || cli.BehindHTTPSProxy 646 }
··· 134 Replicators []string 135 WebsocketURL string 136 BehindHTTPSProxy bool 137 + SegmentDebugDir string 138 } 139 140 // ContentFilters represents the content filtering configuration ··· 196 fs.BoolVar(&cli.PrintChat, "print-chat", false, "print chat messages to stdout") 197 fs.StringVar(&cli.WHIPTest, "whip-test", "", "run a WHIP self-test with the given parameters") 198 fs.StringVar(&cli.RelayHost, "relay-host", "wss://bsky.network", "websocket url for relay firehose") 199 fs.StringVar(&cli.Color, "color", "", "'true' to enable colorized logging, 'false' to disable") 200 fs.StringVar(&cli.BroadcasterHost, "broadcaster-host", "", "public host for the broadcaster group that this node is a part of (excluding https:// e.g. stream.place)") 201 fs.StringVar(&cli.XXDeprecatedPublicHost, "public-host", "", "deprecated, use broadcaster-host or server-host instead as appropriate") 202 fs.StringVar(&cli.ServerHost, "server-host", "", "public host for this particular physical streamplace node. defaults to broadcaster-host and only must be set for multi-node broadcasters") 203 fs.BoolVar(&cli.Thumbnail, "thumbnail", true, "enable thumbnail generation") 204 fs.BoolVar(&cli.SmearAudio, "smear-audio", false, "enable audio smearing to create 'perfect' segment timestamps") 205 fs.StringVar(&cli.TracingEndpoint, "tracing-endpoint", "", "gRPC endpoint to send traces to") 206 fs.IntVar(&cli.RateLimitPerSecond, "rate-limit-per-second", 0, "rate limit for requests per second per ip") 207 fs.IntVar(&cli.RateLimitBurst, "rate-limit-burst", 0, "rate limit burst for requests per ip") ··· 220 fs.BoolVar(&cli.SQLLogging, "sql-logging", false, "enable sql logging") 221 fs.StringVar(&cli.SentryDSN, "sentry-dsn", "", "sentry dsn for error reporting") 222 fs.BoolVar(&cli.LivepeerDebug, "livepeer-debug", false, "log livepeer segments to $SP_DATA_DIR/livepeer-debug") 223 + fs.StringVar(&cli.SegmentDebugDir, "segment-debug-dir", "", "directory to log segment validation to") 224 cli.StringSliceFlag(fs, &cli.Tickets, "tickets", []string{}, "tickets to join the swarm with") 225 fs.StringVar(&cli.IrohTopic, "iroh-topic", "", "topic to use for the iroh swarm (must be 32 bytes in hex)") 226 fs.BoolVar(&cli.DisableIrohRelay, "disable-iroh-relay", false, "disable the iroh relay") ··· 229 cli.StringSliceFlag(fs, &cli.Replicators, "replicators", []string{ReplicatorWebsocket}, "list of replication protocols to use (http, iroh)") 230 fs.StringVar(&cli.WebsocketURL, "websocket-url", "", "override the websocket (ws:// or wss://) url to use for replication (normally not necessary, used for testing)") 231 fs.BoolVar(&cli.BehindHTTPSProxy, "behind-https-proxy", false, "set to true if this node is behind an https proxy and we should report https URLs even though the node isn't serving HTTPS") 232 + 233 + fs.Bool("external-signing", true, "DEPRECATED, does nothing.") 234 + fs.Bool("insecure", false, "DEPRECATED, does nothing.") 235 236 lpFlags := flag.NewFlagSet("livepeer", flag.ContinueOnError) 237 _ = starter.NewLivepeerConfig(lpFlags) ··· 647 func (cli *CLI) HasHTTPS() bool { 648 return cli.Secure || cli.BehindHTTPSProxy 649 } 650 + 651 + func (cli *CLI) DumpDebugSegment(ctx context.Context, name string, r io.Reader) { 652 + if cli.SegmentDebugDir == "" { 653 + return 654 + } 655 + go func() { 656 + err := os.MkdirAll(cli.SegmentDebugDir, 0755) 657 + if err != nil { 658 + log.Error(ctx, "failed to create debug directory", "error", err) 659 + return 660 + } 661 + now := aqtime.FromTime(time.Now()) 662 + outFile := filepath.Join(cli.SegmentDebugDir, fmt.Sprintf("%s-%s", now.FileSafeString(), strings.ReplaceAll(name, ":", "-"))) 663 + fd, err := os.Create(outFile) 664 + if err != nil { 665 + log.Error(ctx, "failed to create debug file", "error", err) 666 + return 667 + } 668 + defer fd.Close() 669 + _, err = io.Copy(fd, r) 670 + if err != nil { 671 + log.Error(ctx, "failed to copy debug file", "error", err) 672 + return 673 + } 674 + log.Log(ctx, "wrote debug file", "path", outFile) 675 + }() 676 + }
-29
pkg/globalerror/globalerror.go
··· 1 - package globalerror 2 - 3 - import ( 4 - "fmt" 5 - "path/filepath" 6 - "runtime" 7 - "sync" 8 - ) 9 - 10 - var GlobalErrors []error 11 - var mut sync.Mutex 12 - 13 - func GlobalError(err error) { 14 - if GlobalErrors == nil { 15 - return 16 - } 17 - _, file, line, _ := runtime.Caller(1) 18 - 19 - go func() { 20 - mut.Lock() 21 - defer mut.Unlock() 22 - _, myfile, _, _ := runtime.Caller(0) 23 - // This assumes that the root directory of streamplace is two levels above this folder. 24 - // If that changes, please update this rootDir resolution. 25 - rootDir := filepath.Join(filepath.Dir(myfile), "..", "..") 26 - rel, _ := filepath.Rel(rootDir, file) 27 - GlobalErrors = append(GlobalErrors, fmt.Errorf("%s:%d: %w", rel, line, err)) 28 - }() 29 - }
···
+931 -6
pkg/iroh/generated/iroh_streamplace/iroh_streamplace.go
··· 338 339 FfiConverterDataHandlerINSTANCE.register() 340 FfiConverterGoSignerINSTANCE.register() 341 uniffiCheckChecksums() 342 } 343 ··· 356 checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 357 return C.uniffi_iroh_streamplace_checksum_func_get_manifest_and_cert() 358 }) 359 - if checksum != 17550 { 360 // If this happens try cleaning and rebuilding your project 361 panic("iroh_streamplace: uniffi_iroh_streamplace_checksum_func_get_manifest_and_cert: UniFFI API checksum mismatch") 362 } 363 } 364 { 365 checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 366 return C.uniffi_iroh_streamplace_checksum_func_init_logging() 367 }) 368 if checksum != 40911 { ··· 390 } 391 { 392 checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 393 return C.uniffi_iroh_streamplace_checksum_func_sign() 394 }) 395 - if checksum != 23786 { 396 // If this happens try cleaning and rebuilding your project 397 panic("iroh_streamplace: uniffi_iroh_streamplace_checksum_func_sign: UniFFI API checksum mismatch") 398 } 399 } 400 { ··· 534 } 535 { 536 checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 537 return C.uniffi_iroh_streamplace_checksum_method_node_add_tickets() 538 }) 539 if checksum != 8701 { ··· 687 } 688 { 689 checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 690 return C.uniffi_iroh_streamplace_checksum_method_subscriberesponse_next_raw() 691 }) 692 if checksum != 55650 { ··· 791 type FfiDestroyerUint64 struct{} 792 793 func (FfiDestroyerUint64) Destroy(_ uint64) {} 794 795 type FfiConverterBool struct{} 796 ··· 1655 C.uniffi_iroh_streamplace_fn_init_callback_vtable_gosigner(&UniffiVTableCallbackInterfaceGoSignerINSTANCE) 1656 } 1657 1658 // Iroh-streamplace node that can send, forward or receive stream segments. 1659 type NodeInterface interface { 1660 // Add tickets for remote peers ··· 2366 value.Destroy() 2367 } 2368 2369 // A response to a subscribe request. 2370 // 2371 // This can be used as a stream of [`SubscribeItem`]s. ··· 3586 // Err* are used for checking error type with `errors.Is` 3587 var ErrSpErrorNoCertificateChainFound = fmt.Errorf("SpErrorNoCertificateChainFound") 3588 var ErrSpErrorC2paError = fmt.Errorf("SpErrorC2paError") 3589 3590 // Variant structs 3591 type SpErrorNoCertificateChainFound struct { ··· 3626 return target == ErrSpErrorC2paError 3627 } 3628 3629 type FfiConverterSpError struct{} 3630 3631 var FfiConverterSpErrorINSTANCE = FfiConverterSpError{} ··· 3647 return &SpError{&SpErrorNoCertificateChainFound{message}} 3648 case 2: 3649 return &SpError{&SpErrorC2paError{message}} 3650 default: 3651 panic(fmt.Sprintf("Unknown error code %d in FfiConverterSpError.Read()", errorID)) 3652 } ··· 3659 writeInt32(writer, 1) 3660 case *SpErrorC2paError: 3661 writeInt32(writer, 2) 3662 default: 3663 _ = variantValue 3664 panic(fmt.Sprintf("invalid error value `%v` in FfiConverterSpError.Write", value)) ··· 3672 case SpErrorNoCertificateChainFound: 3673 variantValue.destroy() 3674 case SpErrorC2paError: 3675 variantValue.destroy() 3676 default: 3677 _ = variantValue ··· 4460 } 4461 } 4462 4463 type FfiConverterOptionalSubscribeItem struct{} 4464 4465 var FfiConverterOptionalSubscribeItemINSTANCE = FfiConverterOptionalSubscribeItem{} ··· 4689 guard <- struct{}{} 4690 } 4691 4692 - func GetManifestAndCert(data []byte) (string, error) { 4693 _uniffiRV, _uniffiErr := rustCallWithError[SpError](FfiConverterSpError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { 4694 return GoRustBuffer{ 4695 - inner: C.uniffi_iroh_streamplace_fn_func_get_manifest_and_cert(FfiConverterBytesINSTANCE.Lower(data), _uniffiStatus), 4696 } 4697 }) 4698 if _uniffiErr != nil { ··· 4737 } 4738 } 4739 4740 - func Sign(manifest string, data []byte, certs []byte, gosigner GoSigner) ([]byte, error) { 4741 _uniffiRV, _uniffiErr := rustCallWithError[SpError](FfiConverterSpError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { 4742 return GoRustBuffer{ 4743 - inner: C.uniffi_iroh_streamplace_fn_func_sign(FfiConverterStringINSTANCE.Lower(manifest), FfiConverterBytesINSTANCE.Lower(data), FfiConverterBytesINSTANCE.Lower(certs), FfiConverterGoSignerINSTANCE.Lower(gosigner), _uniffiStatus), 4744 } 4745 }) 4746 if _uniffiErr != nil { ··· 4749 } else { 4750 return FfiConverterBytesINSTANCE.Lift(_uniffiRV), nil 4751 } 4752 } 4753 4754 func SubscribeItemDebug(item SubscribeItem) string {
··· 338 339 FfiConverterDataHandlerINSTANCE.register() 340 FfiConverterGoSignerINSTANCE.register() 341 + FfiConverterManySegmentsToSignINSTANCE.register() 342 + FfiConverterManyStreamsINSTANCE.register() 343 + FfiConverterSegmentToSignINSTANCE.register() 344 + FfiConverterStreamINSTANCE.register() 345 uniffiCheckChecksums() 346 } 347 ··· 360 checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 361 return C.uniffi_iroh_streamplace_checksum_func_get_manifest_and_cert() 362 }) 363 + if checksum != 36028 { 364 // If this happens try cleaning and rebuilding your project 365 panic("iroh_streamplace: uniffi_iroh_streamplace_checksum_func_get_manifest_and_cert: UniFFI API checksum mismatch") 366 } 367 } 368 { 369 checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 370 + return C.uniffi_iroh_streamplace_checksum_func_get_manifests() 371 + }) 372 + if checksum != 2548 { 373 + // If this happens try cleaning and rebuilding your project 374 + panic("iroh_streamplace: uniffi_iroh_streamplace_checksum_func_get_manifests: UniFFI API checksum mismatch") 375 + } 376 + } 377 + { 378 + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 379 return C.uniffi_iroh_streamplace_checksum_func_init_logging() 380 }) 381 if checksum != 40911 { ··· 403 } 404 { 405 checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 406 + return C.uniffi_iroh_streamplace_checksum_func_resign() 407 + }) 408 + if checksum != 34597 { 409 + // If this happens try cleaning and rebuilding your project 410 + panic("iroh_streamplace: uniffi_iroh_streamplace_checksum_func_resign: UniFFI API checksum mismatch") 411 + } 412 + } 413 + { 414 + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 415 return C.uniffi_iroh_streamplace_checksum_func_sign() 416 }) 417 + if checksum != 41179 { 418 // If this happens try cleaning and rebuilding your project 419 panic("iroh_streamplace: uniffi_iroh_streamplace_checksum_func_sign: UniFFI API checksum mismatch") 420 + } 421 + } 422 + { 423 + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 424 + return C.uniffi_iroh_streamplace_checksum_func_sign_with_ingredients() 425 + }) 426 + if checksum != 15411 { 427 + // If this happens try cleaning and rebuilding your project 428 + panic("iroh_streamplace: uniffi_iroh_streamplace_checksum_func_sign_with_ingredients: UniFFI API checksum mismatch") 429 } 430 } 431 { ··· 565 } 566 { 567 checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 568 + return C.uniffi_iroh_streamplace_checksum_method_manysegmentstosign_next() 569 + }) 570 + if checksum != 53885 { 571 + // If this happens try cleaning and rebuilding your project 572 + panic("iroh_streamplace: uniffi_iroh_streamplace_checksum_method_manysegmentstosign_next: UniFFI API checksum mismatch") 573 + } 574 + } 575 + { 576 + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 577 + return C.uniffi_iroh_streamplace_checksum_method_manystreams_next() 578 + }) 579 + if checksum != 50757 { 580 + // If this happens try cleaning and rebuilding your project 581 + panic("iroh_streamplace: uniffi_iroh_streamplace_checksum_method_manystreams_next: UniFFI API checksum mismatch") 582 + } 583 + } 584 + { 585 + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 586 return C.uniffi_iroh_streamplace_checksum_method_node_add_tickets() 587 }) 588 if checksum != 8701 { ··· 736 } 737 { 738 checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 739 + return C.uniffi_iroh_streamplace_checksum_method_segmenttosign_unsigned_seg_stream() 740 + }) 741 + if checksum != 25252 { 742 + // If this happens try cleaning and rebuilding your project 743 + panic("iroh_streamplace: uniffi_iroh_streamplace_checksum_method_segmenttosign_unsigned_seg_stream: UniFFI API checksum mismatch") 744 + } 745 + } 746 + { 747 + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 748 + return C.uniffi_iroh_streamplace_checksum_method_segmenttosign_manifest_id() 749 + }) 750 + if checksum != 60736 { 751 + // If this happens try cleaning and rebuilding your project 752 + panic("iroh_streamplace: uniffi_iroh_streamplace_checksum_method_segmenttosign_manifest_id: UniFFI API checksum mismatch") 753 + } 754 + } 755 + { 756 + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 757 + return C.uniffi_iroh_streamplace_checksum_method_segmenttosign_cert() 758 + }) 759 + if checksum != 51060 { 760 + // If this happens try cleaning and rebuilding your project 761 + panic("iroh_streamplace: uniffi_iroh_streamplace_checksum_method_segmenttosign_cert: UniFFI API checksum mismatch") 762 + } 763 + } 764 + { 765 + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 766 + return C.uniffi_iroh_streamplace_checksum_method_segmenttosign_output_seg_stream() 767 + }) 768 + if checksum != 6113 { 769 + // If this happens try cleaning and rebuilding your project 770 + panic("iroh_streamplace: uniffi_iroh_streamplace_checksum_method_segmenttosign_output_seg_stream: UniFFI API checksum mismatch") 771 + } 772 + } 773 + { 774 + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 775 + return C.uniffi_iroh_streamplace_checksum_method_segmenttosign_close() 776 + }) 777 + if checksum != 39185 { 778 + // If this happens try cleaning and rebuilding your project 779 + panic("iroh_streamplace: uniffi_iroh_streamplace_checksum_method_segmenttosign_close: UniFFI API checksum mismatch") 780 + } 781 + } 782 + { 783 + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 784 + return C.uniffi_iroh_streamplace_checksum_method_stream_read_stream() 785 + }) 786 + if checksum != 62815 { 787 + // If this happens try cleaning and rebuilding your project 788 + panic("iroh_streamplace: uniffi_iroh_streamplace_checksum_method_stream_read_stream: UniFFI API checksum mismatch") 789 + } 790 + } 791 + { 792 + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 793 + return C.uniffi_iroh_streamplace_checksum_method_stream_seek_stream() 794 + }) 795 + if checksum != 56397 { 796 + // If this happens try cleaning and rebuilding your project 797 + panic("iroh_streamplace: uniffi_iroh_streamplace_checksum_method_stream_seek_stream: UniFFI API checksum mismatch") 798 + } 799 + } 800 + { 801 + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 802 + return C.uniffi_iroh_streamplace_checksum_method_stream_write_stream() 803 + }) 804 + if checksum != 59847 { 805 + // If this happens try cleaning and rebuilding your project 806 + panic("iroh_streamplace: uniffi_iroh_streamplace_checksum_method_stream_write_stream: UniFFI API checksum mismatch") 807 + } 808 + } 809 + { 810 + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { 811 return C.uniffi_iroh_streamplace_checksum_method_subscriberesponse_next_raw() 812 }) 813 if checksum != 55650 { ··· 912 type FfiDestroyerUint64 struct{} 913 914 func (FfiDestroyerUint64) Destroy(_ uint64) {} 915 + 916 + type FfiConverterInt64 struct{} 917 + 918 + var FfiConverterInt64INSTANCE = FfiConverterInt64{} 919 + 920 + func (FfiConverterInt64) Lower(value int64) C.int64_t { 921 + return C.int64_t(value) 922 + } 923 + 924 + func (FfiConverterInt64) Write(writer io.Writer, value int64) { 925 + writeInt64(writer, value) 926 + } 927 + 928 + func (FfiConverterInt64) Lift(value C.int64_t) int64 { 929 + return int64(value) 930 + } 931 + 932 + func (FfiConverterInt64) Read(reader io.Reader) int64 { 933 + return readInt64(reader) 934 + } 935 + 936 + type FfiDestroyerInt64 struct{} 937 + 938 + func (FfiDestroyerInt64) Destroy(_ int64) {} 939 940 type FfiConverterBool struct{} 941 ··· 1800 C.uniffi_iroh_streamplace_fn_init_callback_vtable_gosigner(&UniffiVTableCallbackInterfaceGoSignerINSTANCE) 1801 } 1802 1803 + type ManySegmentsToSign interface { 1804 + Next() *SegmentToSign 1805 + } 1806 + type ManySegmentsToSignImpl struct { 1807 + ffiObject FfiObject 1808 + } 1809 + 1810 + func (_self *ManySegmentsToSignImpl) Next() *SegmentToSign { 1811 + _pointer := _self.ffiObject.incrementPointer("ManySegmentsToSign") 1812 + defer _self.ffiObject.decrementPointer() 1813 + return FfiConverterOptionalSegmentToSignINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI { 1814 + return GoRustBuffer{ 1815 + inner: C.uniffi_iroh_streamplace_fn_method_manysegmentstosign_next( 1816 + _pointer, _uniffiStatus), 1817 + } 1818 + })) 1819 + } 1820 + func (object *ManySegmentsToSignImpl) Destroy() { 1821 + runtime.SetFinalizer(object, nil) 1822 + object.ffiObject.destroy() 1823 + } 1824 + 1825 + type FfiConverterManySegmentsToSign struct { 1826 + handleMap *concurrentHandleMap[ManySegmentsToSign] 1827 + } 1828 + 1829 + var FfiConverterManySegmentsToSignINSTANCE = FfiConverterManySegmentsToSign{ 1830 + handleMap: newConcurrentHandleMap[ManySegmentsToSign](), 1831 + } 1832 + 1833 + func (c FfiConverterManySegmentsToSign) Lift(pointer unsafe.Pointer) ManySegmentsToSign { 1834 + result := &ManySegmentsToSignImpl{ 1835 + newFfiObject( 1836 + pointer, 1837 + func(pointer unsafe.Pointer, status *C.RustCallStatus) unsafe.Pointer { 1838 + return C.uniffi_iroh_streamplace_fn_clone_manysegmentstosign(pointer, status) 1839 + }, 1840 + func(pointer unsafe.Pointer, status *C.RustCallStatus) { 1841 + C.uniffi_iroh_streamplace_fn_free_manysegmentstosign(pointer, status) 1842 + }, 1843 + ), 1844 + } 1845 + runtime.SetFinalizer(result, (*ManySegmentsToSignImpl).Destroy) 1846 + return result 1847 + } 1848 + 1849 + func (c FfiConverterManySegmentsToSign) Read(reader io.Reader) ManySegmentsToSign { 1850 + return c.Lift(unsafe.Pointer(uintptr(readUint64(reader)))) 1851 + } 1852 + 1853 + func (c FfiConverterManySegmentsToSign) Lower(value ManySegmentsToSign) unsafe.Pointer { 1854 + // TODO: this is bad - all synchronization from ObjectRuntime.go is discarded here, 1855 + // because the pointer will be decremented immediately after this function returns, 1856 + // and someone will be left holding onto a non-locked pointer. 1857 + pointer := unsafe.Pointer(uintptr(c.handleMap.insert(value))) 1858 + return pointer 1859 + 1860 + } 1861 + 1862 + func (c FfiConverterManySegmentsToSign) Write(writer io.Writer, value ManySegmentsToSign) { 1863 + writeUint64(writer, uint64(uintptr(c.Lower(value)))) 1864 + } 1865 + 1866 + type FfiDestroyerManySegmentsToSign struct{} 1867 + 1868 + func (_ FfiDestroyerManySegmentsToSign) Destroy(value ManySegmentsToSign) { 1869 + if val, ok := value.(*ManySegmentsToSignImpl); ok { 1870 + val.Destroy() 1871 + } else { 1872 + panic("Expected *ManySegmentsToSignImpl") 1873 + } 1874 + } 1875 + 1876 + //export iroh_streamplace_cgo_dispatchCallbackInterfaceManySegmentsToSignMethod0 1877 + func iroh_streamplace_cgo_dispatchCallbackInterfaceManySegmentsToSignMethod0(uniffiHandle C.uint64_t, uniffiOutReturn *C.RustBuffer, callStatus *C.RustCallStatus) { 1878 + handle := uint64(uniffiHandle) 1879 + uniffiObj, ok := FfiConverterManySegmentsToSignINSTANCE.handleMap.tryGet(handle) 1880 + if !ok { 1881 + panic(fmt.Errorf("no callback in handle map: %d", handle)) 1882 + } 1883 + 1884 + res := 1885 + uniffiObj.Next() 1886 + 1887 + *uniffiOutReturn = FfiConverterOptionalSegmentToSignINSTANCE.Lower(res) 1888 + } 1889 + 1890 + var UniffiVTableCallbackInterfaceManySegmentsToSignINSTANCE = C.UniffiVTableCallbackInterfaceManySegmentsToSign{ 1891 + next: (C.UniffiCallbackInterfaceManySegmentsToSignMethod0)(C.iroh_streamplace_cgo_dispatchCallbackInterfaceManySegmentsToSignMethod0), 1892 + 1893 + uniffiFree: (C.UniffiCallbackInterfaceFree)(C.iroh_streamplace_cgo_dispatchCallbackInterfaceManySegmentsToSignFree), 1894 + } 1895 + 1896 + //export iroh_streamplace_cgo_dispatchCallbackInterfaceManySegmentsToSignFree 1897 + func iroh_streamplace_cgo_dispatchCallbackInterfaceManySegmentsToSignFree(handle C.uint64_t) { 1898 + FfiConverterManySegmentsToSignINSTANCE.handleMap.remove(uint64(handle)) 1899 + } 1900 + 1901 + func (c FfiConverterManySegmentsToSign) register() { 1902 + C.uniffi_iroh_streamplace_fn_init_callback_vtable_manysegmentstosign(&UniffiVTableCallbackInterfaceManySegmentsToSignINSTANCE) 1903 + } 1904 + 1905 + type ManyStreams interface { 1906 + // Get the next stream from the many streams 1907 + Next() *Stream 1908 + } 1909 + type ManyStreamsImpl struct { 1910 + ffiObject FfiObject 1911 + } 1912 + 1913 + // Get the next stream from the many streams 1914 + func (_self *ManyStreamsImpl) Next() *Stream { 1915 + _pointer := _self.ffiObject.incrementPointer("ManyStreams") 1916 + defer _self.ffiObject.decrementPointer() 1917 + return FfiConverterOptionalStreamINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI { 1918 + return GoRustBuffer{ 1919 + inner: C.uniffi_iroh_streamplace_fn_method_manystreams_next( 1920 + _pointer, _uniffiStatus), 1921 + } 1922 + })) 1923 + } 1924 + func (object *ManyStreamsImpl) Destroy() { 1925 + runtime.SetFinalizer(object, nil) 1926 + object.ffiObject.destroy() 1927 + } 1928 + 1929 + type FfiConverterManyStreams struct { 1930 + handleMap *concurrentHandleMap[ManyStreams] 1931 + } 1932 + 1933 + var FfiConverterManyStreamsINSTANCE = FfiConverterManyStreams{ 1934 + handleMap: newConcurrentHandleMap[ManyStreams](), 1935 + } 1936 + 1937 + func (c FfiConverterManyStreams) Lift(pointer unsafe.Pointer) ManyStreams { 1938 + result := &ManyStreamsImpl{ 1939 + newFfiObject( 1940 + pointer, 1941 + func(pointer unsafe.Pointer, status *C.RustCallStatus) unsafe.Pointer { 1942 + return C.uniffi_iroh_streamplace_fn_clone_manystreams(pointer, status) 1943 + }, 1944 + func(pointer unsafe.Pointer, status *C.RustCallStatus) { 1945 + C.uniffi_iroh_streamplace_fn_free_manystreams(pointer, status) 1946 + }, 1947 + ), 1948 + } 1949 + runtime.SetFinalizer(result, (*ManyStreamsImpl).Destroy) 1950 + return result 1951 + } 1952 + 1953 + func (c FfiConverterManyStreams) Read(reader io.Reader) ManyStreams { 1954 + return c.Lift(unsafe.Pointer(uintptr(readUint64(reader)))) 1955 + } 1956 + 1957 + func (c FfiConverterManyStreams) Lower(value ManyStreams) unsafe.Pointer { 1958 + // TODO: this is bad - all synchronization from ObjectRuntime.go is discarded here, 1959 + // because the pointer will be decremented immediately after this function returns, 1960 + // and someone will be left holding onto a non-locked pointer. 1961 + pointer := unsafe.Pointer(uintptr(c.handleMap.insert(value))) 1962 + return pointer 1963 + 1964 + } 1965 + 1966 + func (c FfiConverterManyStreams) Write(writer io.Writer, value ManyStreams) { 1967 + writeUint64(writer, uint64(uintptr(c.Lower(value)))) 1968 + } 1969 + 1970 + type FfiDestroyerManyStreams struct{} 1971 + 1972 + func (_ FfiDestroyerManyStreams) Destroy(value ManyStreams) { 1973 + if val, ok := value.(*ManyStreamsImpl); ok { 1974 + val.Destroy() 1975 + } else { 1976 + panic("Expected *ManyStreamsImpl") 1977 + } 1978 + } 1979 + 1980 + //export iroh_streamplace_cgo_dispatchCallbackInterfaceManyStreamsMethod0 1981 + func iroh_streamplace_cgo_dispatchCallbackInterfaceManyStreamsMethod0(uniffiHandle C.uint64_t, uniffiOutReturn *C.RustBuffer, callStatus *C.RustCallStatus) { 1982 + handle := uint64(uniffiHandle) 1983 + uniffiObj, ok := FfiConverterManyStreamsINSTANCE.handleMap.tryGet(handle) 1984 + if !ok { 1985 + panic(fmt.Errorf("no callback in handle map: %d", handle)) 1986 + } 1987 + 1988 + res := 1989 + uniffiObj.Next() 1990 + 1991 + *uniffiOutReturn = FfiConverterOptionalStreamINSTANCE.Lower(res) 1992 + } 1993 + 1994 + var UniffiVTableCallbackInterfaceManyStreamsINSTANCE = C.UniffiVTableCallbackInterfaceManyStreams{ 1995 + next: (C.UniffiCallbackInterfaceManyStreamsMethod0)(C.iroh_streamplace_cgo_dispatchCallbackInterfaceManyStreamsMethod0), 1996 + 1997 + uniffiFree: (C.UniffiCallbackInterfaceFree)(C.iroh_streamplace_cgo_dispatchCallbackInterfaceManyStreamsFree), 1998 + } 1999 + 2000 + //export iroh_streamplace_cgo_dispatchCallbackInterfaceManyStreamsFree 2001 + func iroh_streamplace_cgo_dispatchCallbackInterfaceManyStreamsFree(handle C.uint64_t) { 2002 + FfiConverterManyStreamsINSTANCE.handleMap.remove(uint64(handle)) 2003 + } 2004 + 2005 + func (c FfiConverterManyStreams) register() { 2006 + C.uniffi_iroh_streamplace_fn_init_callback_vtable_manystreams(&UniffiVTableCallbackInterfaceManyStreamsINSTANCE) 2007 + } 2008 + 2009 // Iroh-streamplace node that can send, forward or receive stream segments. 2010 type NodeInterface interface { 2011 // Add tickets for remote peers ··· 2717 value.Destroy() 2718 } 2719 2720 + type SegmentToSign interface { 2721 + UnsignedSegStream() Stream 2722 + ManifestId() string 2723 + Cert() []byte 2724 + OutputSegStream() Stream 2725 + Close() 2726 + } 2727 + type SegmentToSignImpl struct { 2728 + ffiObject FfiObject 2729 + } 2730 + 2731 + func (_self *SegmentToSignImpl) UnsignedSegStream() Stream { 2732 + _pointer := _self.ffiObject.incrementPointer("SegmentToSign") 2733 + defer _self.ffiObject.decrementPointer() 2734 + return FfiConverterStreamINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) unsafe.Pointer { 2735 + return C.uniffi_iroh_streamplace_fn_method_segmenttosign_unsigned_seg_stream( 2736 + _pointer, _uniffiStatus) 2737 + })) 2738 + } 2739 + 2740 + func (_self *SegmentToSignImpl) ManifestId() string { 2741 + _pointer := _self.ffiObject.incrementPointer("SegmentToSign") 2742 + defer _self.ffiObject.decrementPointer() 2743 + return FfiConverterStringINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI { 2744 + return GoRustBuffer{ 2745 + inner: C.uniffi_iroh_streamplace_fn_method_segmenttosign_manifest_id( 2746 + _pointer, _uniffiStatus), 2747 + } 2748 + })) 2749 + } 2750 + 2751 + func (_self *SegmentToSignImpl) Cert() []byte { 2752 + _pointer := _self.ffiObject.incrementPointer("SegmentToSign") 2753 + defer _self.ffiObject.decrementPointer() 2754 + return FfiConverterBytesINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI { 2755 + return GoRustBuffer{ 2756 + inner: C.uniffi_iroh_streamplace_fn_method_segmenttosign_cert( 2757 + _pointer, _uniffiStatus), 2758 + } 2759 + })) 2760 + } 2761 + 2762 + func (_self *SegmentToSignImpl) OutputSegStream() Stream { 2763 + _pointer := _self.ffiObject.incrementPointer("SegmentToSign") 2764 + defer _self.ffiObject.decrementPointer() 2765 + return FfiConverterStreamINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) unsafe.Pointer { 2766 + return C.uniffi_iroh_streamplace_fn_method_segmenttosign_output_seg_stream( 2767 + _pointer, _uniffiStatus) 2768 + })) 2769 + } 2770 + 2771 + func (_self *SegmentToSignImpl) Close() { 2772 + _pointer := _self.ffiObject.incrementPointer("SegmentToSign") 2773 + defer _self.ffiObject.decrementPointer() 2774 + rustCall(func(_uniffiStatus *C.RustCallStatus) bool { 2775 + C.uniffi_iroh_streamplace_fn_method_segmenttosign_close( 2776 + _pointer, _uniffiStatus) 2777 + return false 2778 + }) 2779 + } 2780 + func (object *SegmentToSignImpl) Destroy() { 2781 + runtime.SetFinalizer(object, nil) 2782 + object.ffiObject.destroy() 2783 + } 2784 + 2785 + type FfiConverterSegmentToSign struct { 2786 + handleMap *concurrentHandleMap[SegmentToSign] 2787 + } 2788 + 2789 + var FfiConverterSegmentToSignINSTANCE = FfiConverterSegmentToSign{ 2790 + handleMap: newConcurrentHandleMap[SegmentToSign](), 2791 + } 2792 + 2793 + func (c FfiConverterSegmentToSign) Lift(pointer unsafe.Pointer) SegmentToSign { 2794 + result := &SegmentToSignImpl{ 2795 + newFfiObject( 2796 + pointer, 2797 + func(pointer unsafe.Pointer, status *C.RustCallStatus) unsafe.Pointer { 2798 + return C.uniffi_iroh_streamplace_fn_clone_segmenttosign(pointer, status) 2799 + }, 2800 + func(pointer unsafe.Pointer, status *C.RustCallStatus) { 2801 + C.uniffi_iroh_streamplace_fn_free_segmenttosign(pointer, status) 2802 + }, 2803 + ), 2804 + } 2805 + runtime.SetFinalizer(result, (*SegmentToSignImpl).Destroy) 2806 + return result 2807 + } 2808 + 2809 + func (c FfiConverterSegmentToSign) Read(reader io.Reader) SegmentToSign { 2810 + return c.Lift(unsafe.Pointer(uintptr(readUint64(reader)))) 2811 + } 2812 + 2813 + func (c FfiConverterSegmentToSign) Lower(value SegmentToSign) unsafe.Pointer { 2814 + // TODO: this is bad - all synchronization from ObjectRuntime.go is discarded here, 2815 + // because the pointer will be decremented immediately after this function returns, 2816 + // and someone will be left holding onto a non-locked pointer. 2817 + pointer := unsafe.Pointer(uintptr(c.handleMap.insert(value))) 2818 + return pointer 2819 + 2820 + } 2821 + 2822 + func (c FfiConverterSegmentToSign) Write(writer io.Writer, value SegmentToSign) { 2823 + writeUint64(writer, uint64(uintptr(c.Lower(value)))) 2824 + } 2825 + 2826 + type FfiDestroyerSegmentToSign struct{} 2827 + 2828 + func (_ FfiDestroyerSegmentToSign) Destroy(value SegmentToSign) { 2829 + if val, ok := value.(*SegmentToSignImpl); ok { 2830 + val.Destroy() 2831 + } else { 2832 + panic("Expected *SegmentToSignImpl") 2833 + } 2834 + } 2835 + 2836 + //export iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod0 2837 + func iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod0(uniffiHandle C.uint64_t, uniffiOutReturn *unsafe.Pointer, callStatus *C.RustCallStatus) { 2838 + handle := uint64(uniffiHandle) 2839 + uniffiObj, ok := FfiConverterSegmentToSignINSTANCE.handleMap.tryGet(handle) 2840 + if !ok { 2841 + panic(fmt.Errorf("no callback in handle map: %d", handle)) 2842 + } 2843 + 2844 + res := 2845 + uniffiObj.UnsignedSegStream() 2846 + 2847 + *uniffiOutReturn = FfiConverterStreamINSTANCE.Lower(res) 2848 + } 2849 + 2850 + //export iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod1 2851 + func iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod1(uniffiHandle C.uint64_t, uniffiOutReturn *C.RustBuffer, callStatus *C.RustCallStatus) { 2852 + handle := uint64(uniffiHandle) 2853 + uniffiObj, ok := FfiConverterSegmentToSignINSTANCE.handleMap.tryGet(handle) 2854 + if !ok { 2855 + panic(fmt.Errorf("no callback in handle map: %d", handle)) 2856 + } 2857 + 2858 + res := 2859 + uniffiObj.ManifestId() 2860 + 2861 + *uniffiOutReturn = FfiConverterStringINSTANCE.Lower(res) 2862 + } 2863 + 2864 + //export iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod2 2865 + func iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod2(uniffiHandle C.uint64_t, uniffiOutReturn *C.RustBuffer, callStatus *C.RustCallStatus) { 2866 + handle := uint64(uniffiHandle) 2867 + uniffiObj, ok := FfiConverterSegmentToSignINSTANCE.handleMap.tryGet(handle) 2868 + if !ok { 2869 + panic(fmt.Errorf("no callback in handle map: %d", handle)) 2870 + } 2871 + 2872 + res := 2873 + uniffiObj.Cert() 2874 + 2875 + *uniffiOutReturn = FfiConverterBytesINSTANCE.Lower(res) 2876 + } 2877 + 2878 + //export iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod3 2879 + func iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod3(uniffiHandle C.uint64_t, uniffiOutReturn *unsafe.Pointer, callStatus *C.RustCallStatus) { 2880 + handle := uint64(uniffiHandle) 2881 + uniffiObj, ok := FfiConverterSegmentToSignINSTANCE.handleMap.tryGet(handle) 2882 + if !ok { 2883 + panic(fmt.Errorf("no callback in handle map: %d", handle)) 2884 + } 2885 + 2886 + res := 2887 + uniffiObj.OutputSegStream() 2888 + 2889 + *uniffiOutReturn = FfiConverterStreamINSTANCE.Lower(res) 2890 + } 2891 + 2892 + //export iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod4 2893 + func iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod4(uniffiHandle C.uint64_t, uniffiOutReturn *C.void, callStatus *C.RustCallStatus) { 2894 + handle := uint64(uniffiHandle) 2895 + uniffiObj, ok := FfiConverterSegmentToSignINSTANCE.handleMap.tryGet(handle) 2896 + if !ok { 2897 + panic(fmt.Errorf("no callback in handle map: %d", handle)) 2898 + } 2899 + 2900 + uniffiObj.Close() 2901 + 2902 + } 2903 + 2904 + var UniffiVTableCallbackInterfaceSegmentToSignINSTANCE = C.UniffiVTableCallbackInterfaceSegmentToSign{ 2905 + unsignedSegStream: (C.UniffiCallbackInterfaceSegmentToSignMethod0)(C.iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod0), 2906 + manifestId: (C.UniffiCallbackInterfaceSegmentToSignMethod1)(C.iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod1), 2907 + cert: (C.UniffiCallbackInterfaceSegmentToSignMethod2)(C.iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod2), 2908 + outputSegStream: (C.UniffiCallbackInterfaceSegmentToSignMethod3)(C.iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod3), 2909 + close: (C.UniffiCallbackInterfaceSegmentToSignMethod4)(C.iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod4), 2910 + 2911 + uniffiFree: (C.UniffiCallbackInterfaceFree)(C.iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignFree), 2912 + } 2913 + 2914 + //export iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignFree 2915 + func iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignFree(handle C.uint64_t) { 2916 + FfiConverterSegmentToSignINSTANCE.handleMap.remove(uint64(handle)) 2917 + } 2918 + 2919 + func (c FfiConverterSegmentToSign) register() { 2920 + C.uniffi_iroh_streamplace_fn_init_callback_vtable_segmenttosign(&UniffiVTableCallbackInterfaceSegmentToSignINSTANCE) 2921 + } 2922 + 2923 + // This allows for a callback stream over the Uniffi interface. 2924 + // Implement these stream functions in the foreign language 2925 + // and this will provide Rust Stream trait implementations 2926 + // This is necessary since the Rust traits cannot be implemented directly 2927 + // as uniffi callbacks 2928 + type Stream interface { 2929 + // Read a stream of bytes from the stream 2930 + ReadStream(length uint64) ([]byte, error) 2931 + // Seek to a position in the stream 2932 + SeekStream(pos int64, mode uint64) (uint64, error) 2933 + // Write a stream of bytes to the stream 2934 + WriteStream(data []byte) (uint64, error) 2935 + } 2936 + 2937 + // This allows for a callback stream over the Uniffi interface. 2938 + // Implement these stream functions in the foreign language 2939 + // and this will provide Rust Stream trait implementations 2940 + // This is necessary since the Rust traits cannot be implemented directly 2941 + // as uniffi callbacks 2942 + type StreamImpl struct { 2943 + ffiObject FfiObject 2944 + } 2945 + 2946 + // Read a stream of bytes from the stream 2947 + func (_self *StreamImpl) ReadStream(length uint64) ([]byte, error) { 2948 + _pointer := _self.ffiObject.incrementPointer("Stream") 2949 + defer _self.ffiObject.decrementPointer() 2950 + _uniffiRV, _uniffiErr := rustCallWithError[SpError](FfiConverterSpError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { 2951 + return GoRustBuffer{ 2952 + inner: C.uniffi_iroh_streamplace_fn_method_stream_read_stream( 2953 + _pointer, FfiConverterUint64INSTANCE.Lower(length), _uniffiStatus), 2954 + } 2955 + }) 2956 + if _uniffiErr != nil { 2957 + var _uniffiDefaultValue []byte 2958 + return _uniffiDefaultValue, _uniffiErr 2959 + } else { 2960 + return FfiConverterBytesINSTANCE.Lift(_uniffiRV), nil 2961 + } 2962 + } 2963 + 2964 + // Seek to a position in the stream 2965 + func (_self *StreamImpl) SeekStream(pos int64, mode uint64) (uint64, error) { 2966 + _pointer := _self.ffiObject.incrementPointer("Stream") 2967 + defer _self.ffiObject.decrementPointer() 2968 + _uniffiRV, _uniffiErr := rustCallWithError[SpError](FfiConverterSpError{}, func(_uniffiStatus *C.RustCallStatus) C.uint64_t { 2969 + return C.uniffi_iroh_streamplace_fn_method_stream_seek_stream( 2970 + _pointer, FfiConverterInt64INSTANCE.Lower(pos), FfiConverterUint64INSTANCE.Lower(mode), _uniffiStatus) 2971 + }) 2972 + if _uniffiErr != nil { 2973 + var _uniffiDefaultValue uint64 2974 + return _uniffiDefaultValue, _uniffiErr 2975 + } else { 2976 + return FfiConverterUint64INSTANCE.Lift(_uniffiRV), nil 2977 + } 2978 + } 2979 + 2980 + // Write a stream of bytes to the stream 2981 + func (_self *StreamImpl) WriteStream(data []byte) (uint64, error) { 2982 + _pointer := _self.ffiObject.incrementPointer("Stream") 2983 + defer _self.ffiObject.decrementPointer() 2984 + _uniffiRV, _uniffiErr := rustCallWithError[SpError](FfiConverterSpError{}, func(_uniffiStatus *C.RustCallStatus) C.uint64_t { 2985 + return C.uniffi_iroh_streamplace_fn_method_stream_write_stream( 2986 + _pointer, FfiConverterBytesINSTANCE.Lower(data), _uniffiStatus) 2987 + }) 2988 + if _uniffiErr != nil { 2989 + var _uniffiDefaultValue uint64 2990 + return _uniffiDefaultValue, _uniffiErr 2991 + } else { 2992 + return FfiConverterUint64INSTANCE.Lift(_uniffiRV), nil 2993 + } 2994 + } 2995 + func (object *StreamImpl) Destroy() { 2996 + runtime.SetFinalizer(object, nil) 2997 + object.ffiObject.destroy() 2998 + } 2999 + 3000 + type FfiConverterStream struct { 3001 + handleMap *concurrentHandleMap[Stream] 3002 + } 3003 + 3004 + var FfiConverterStreamINSTANCE = FfiConverterStream{ 3005 + handleMap: newConcurrentHandleMap[Stream](), 3006 + } 3007 + 3008 + func (c FfiConverterStream) Lift(pointer unsafe.Pointer) Stream { 3009 + result := &StreamImpl{ 3010 + newFfiObject( 3011 + pointer, 3012 + func(pointer unsafe.Pointer, status *C.RustCallStatus) unsafe.Pointer { 3013 + return C.uniffi_iroh_streamplace_fn_clone_stream(pointer, status) 3014 + }, 3015 + func(pointer unsafe.Pointer, status *C.RustCallStatus) { 3016 + C.uniffi_iroh_streamplace_fn_free_stream(pointer, status) 3017 + }, 3018 + ), 3019 + } 3020 + runtime.SetFinalizer(result, (*StreamImpl).Destroy) 3021 + return result 3022 + } 3023 + 3024 + func (c FfiConverterStream) Read(reader io.Reader) Stream { 3025 + return c.Lift(unsafe.Pointer(uintptr(readUint64(reader)))) 3026 + } 3027 + 3028 + func (c FfiConverterStream) Lower(value Stream) unsafe.Pointer { 3029 + // TODO: this is bad - all synchronization from ObjectRuntime.go is discarded here, 3030 + // because the pointer will be decremented immediately after this function returns, 3031 + // and someone will be left holding onto a non-locked pointer. 3032 + pointer := unsafe.Pointer(uintptr(c.handleMap.insert(value))) 3033 + return pointer 3034 + 3035 + } 3036 + 3037 + func (c FfiConverterStream) Write(writer io.Writer, value Stream) { 3038 + writeUint64(writer, uint64(uintptr(c.Lower(value)))) 3039 + } 3040 + 3041 + type FfiDestroyerStream struct{} 3042 + 3043 + func (_ FfiDestroyerStream) Destroy(value Stream) { 3044 + if val, ok := value.(*StreamImpl); ok { 3045 + val.Destroy() 3046 + } else { 3047 + panic("Expected *StreamImpl") 3048 + } 3049 + } 3050 + 3051 + //export iroh_streamplace_cgo_dispatchCallbackInterfaceStreamMethod0 3052 + func iroh_streamplace_cgo_dispatchCallbackInterfaceStreamMethod0(uniffiHandle C.uint64_t, length C.uint64_t, uniffiOutReturn *C.RustBuffer, callStatus *C.RustCallStatus) { 3053 + handle := uint64(uniffiHandle) 3054 + uniffiObj, ok := FfiConverterStreamINSTANCE.handleMap.tryGet(handle) 3055 + if !ok { 3056 + panic(fmt.Errorf("no callback in handle map: %d", handle)) 3057 + } 3058 + 3059 + res, err := 3060 + uniffiObj.ReadStream( 3061 + FfiConverterUint64INSTANCE.Lift(length), 3062 + ) 3063 + 3064 + if err != nil { 3065 + var actualError *SpError 3066 + if errors.As(err, &actualError) { 3067 + *callStatus = C.RustCallStatus{ 3068 + code: C.int8_t(uniffiCallbackResultError), 3069 + errorBuf: FfiConverterSpErrorINSTANCE.Lower(actualError), 3070 + } 3071 + } else { 3072 + *callStatus = C.RustCallStatus{ 3073 + code: C.int8_t(uniffiCallbackUnexpectedResultError), 3074 + } 3075 + } 3076 + return 3077 + } 3078 + 3079 + *uniffiOutReturn = FfiConverterBytesINSTANCE.Lower(res) 3080 + } 3081 + 3082 + //export iroh_streamplace_cgo_dispatchCallbackInterfaceStreamMethod1 3083 + func iroh_streamplace_cgo_dispatchCallbackInterfaceStreamMethod1(uniffiHandle C.uint64_t, pos C.int64_t, mode C.uint64_t, uniffiOutReturn *C.uint64_t, callStatus *C.RustCallStatus) { 3084 + handle := uint64(uniffiHandle) 3085 + uniffiObj, ok := FfiConverterStreamINSTANCE.handleMap.tryGet(handle) 3086 + if !ok { 3087 + panic(fmt.Errorf("no callback in handle map: %d", handle)) 3088 + } 3089 + 3090 + res, err := 3091 + uniffiObj.SeekStream( 3092 + FfiConverterInt64INSTANCE.Lift(pos), 3093 + FfiConverterUint64INSTANCE.Lift(mode), 3094 + ) 3095 + 3096 + if err != nil { 3097 + var actualError *SpError 3098 + if errors.As(err, &actualError) { 3099 + *callStatus = C.RustCallStatus{ 3100 + code: C.int8_t(uniffiCallbackResultError), 3101 + errorBuf: FfiConverterSpErrorINSTANCE.Lower(actualError), 3102 + } 3103 + } else { 3104 + *callStatus = C.RustCallStatus{ 3105 + code: C.int8_t(uniffiCallbackUnexpectedResultError), 3106 + } 3107 + } 3108 + return 3109 + } 3110 + 3111 + *uniffiOutReturn = FfiConverterUint64INSTANCE.Lower(res) 3112 + } 3113 + 3114 + //export iroh_streamplace_cgo_dispatchCallbackInterfaceStreamMethod2 3115 + func iroh_streamplace_cgo_dispatchCallbackInterfaceStreamMethod2(uniffiHandle C.uint64_t, data C.RustBuffer, uniffiOutReturn *C.uint64_t, callStatus *C.RustCallStatus) { 3116 + handle := uint64(uniffiHandle) 3117 + uniffiObj, ok := FfiConverterStreamINSTANCE.handleMap.tryGet(handle) 3118 + if !ok { 3119 + panic(fmt.Errorf("no callback in handle map: %d", handle)) 3120 + } 3121 + 3122 + res, err := 3123 + uniffiObj.WriteStream( 3124 + FfiConverterBytesINSTANCE.Lift(GoRustBuffer{ 3125 + inner: data, 3126 + }), 3127 + ) 3128 + 3129 + if err != nil { 3130 + var actualError *SpError 3131 + if errors.As(err, &actualError) { 3132 + *callStatus = C.RustCallStatus{ 3133 + code: C.int8_t(uniffiCallbackResultError), 3134 + errorBuf: FfiConverterSpErrorINSTANCE.Lower(actualError), 3135 + } 3136 + } else { 3137 + *callStatus = C.RustCallStatus{ 3138 + code: C.int8_t(uniffiCallbackUnexpectedResultError), 3139 + } 3140 + } 3141 + return 3142 + } 3143 + 3144 + *uniffiOutReturn = FfiConverterUint64INSTANCE.Lower(res) 3145 + } 3146 + 3147 + var UniffiVTableCallbackInterfaceStreamINSTANCE = C.UniffiVTableCallbackInterfaceStream{ 3148 + readStream: (C.UniffiCallbackInterfaceStreamMethod0)(C.iroh_streamplace_cgo_dispatchCallbackInterfaceStreamMethod0), 3149 + seekStream: (C.UniffiCallbackInterfaceStreamMethod1)(C.iroh_streamplace_cgo_dispatchCallbackInterfaceStreamMethod1), 3150 + writeStream: (C.UniffiCallbackInterfaceStreamMethod2)(C.iroh_streamplace_cgo_dispatchCallbackInterfaceStreamMethod2), 3151 + 3152 + uniffiFree: (C.UniffiCallbackInterfaceFree)(C.iroh_streamplace_cgo_dispatchCallbackInterfaceStreamFree), 3153 + } 3154 + 3155 + //export iroh_streamplace_cgo_dispatchCallbackInterfaceStreamFree 3156 + func iroh_streamplace_cgo_dispatchCallbackInterfaceStreamFree(handle C.uint64_t) { 3157 + FfiConverterStreamINSTANCE.handleMap.remove(uint64(handle)) 3158 + } 3159 + 3160 + func (c FfiConverterStream) register() { 3161 + C.uniffi_iroh_streamplace_fn_init_callback_vtable_stream(&UniffiVTableCallbackInterfaceStreamINSTANCE) 3162 + } 3163 + 3164 // A response to a subscribe request. 3165 // 3166 // This can be used as a stream of [`SubscribeItem`]s. ··· 4381 // Err* are used for checking error type with `errors.Is` 4382 var ErrSpErrorNoCertificateChainFound = fmt.Errorf("SpErrorNoCertificateChainFound") 4383 var ErrSpErrorC2paError = fmt.Errorf("SpErrorC2paError") 4384 + var ErrSpErrorIoError = fmt.Errorf("SpErrorIoError") 4385 4386 // Variant structs 4387 type SpErrorNoCertificateChainFound struct { ··· 4422 return target == ErrSpErrorC2paError 4423 } 4424 4425 + type SpErrorIoError struct { 4426 + message string 4427 + } 4428 + 4429 + func NewSpErrorIoError() *SpError { 4430 + return &SpError{err: &SpErrorIoError{}} 4431 + } 4432 + 4433 + func (e SpErrorIoError) destroy() { 4434 + } 4435 + 4436 + func (err SpErrorIoError) Error() string { 4437 + return fmt.Sprintf("IoError: %s", err.message) 4438 + } 4439 + 4440 + func (self SpErrorIoError) Is(target error) bool { 4441 + return target == ErrSpErrorIoError 4442 + } 4443 + 4444 type FfiConverterSpError struct{} 4445 4446 var FfiConverterSpErrorINSTANCE = FfiConverterSpError{} ··· 4462 return &SpError{&SpErrorNoCertificateChainFound{message}} 4463 case 2: 4464 return &SpError{&SpErrorC2paError{message}} 4465 + case 3: 4466 + return &SpError{&SpErrorIoError{message}} 4467 default: 4468 panic(fmt.Sprintf("Unknown error code %d in FfiConverterSpError.Read()", errorID)) 4469 } ··· 4476 writeInt32(writer, 1) 4477 case *SpErrorC2paError: 4478 writeInt32(writer, 2) 4479 + case *SpErrorIoError: 4480 + writeInt32(writer, 3) 4481 default: 4482 _ = variantValue 4483 panic(fmt.Sprintf("invalid error value `%v` in FfiConverterSpError.Write", value)) ··· 4491 case SpErrorNoCertificateChainFound: 4492 variantValue.destroy() 4493 case SpErrorC2paError: 4494 + variantValue.destroy() 4495 + case SpErrorIoError: 4496 variantValue.destroy() 4497 default: 4498 _ = variantValue ··· 5281 } 5282 } 5283 5284 + type FfiConverterOptionalSegmentToSign struct{} 5285 + 5286 + var FfiConverterOptionalSegmentToSignINSTANCE = FfiConverterOptionalSegmentToSign{} 5287 + 5288 + func (c FfiConverterOptionalSegmentToSign) Lift(rb RustBufferI) *SegmentToSign { 5289 + return LiftFromRustBuffer[*SegmentToSign](c, rb) 5290 + } 5291 + 5292 + func (_ FfiConverterOptionalSegmentToSign) Read(reader io.Reader) *SegmentToSign { 5293 + if readInt8(reader) == 0 { 5294 + return nil 5295 + } 5296 + temp := FfiConverterSegmentToSignINSTANCE.Read(reader) 5297 + return &temp 5298 + } 5299 + 5300 + func (c FfiConverterOptionalSegmentToSign) Lower(value *SegmentToSign) C.RustBuffer { 5301 + return LowerIntoRustBuffer[*SegmentToSign](c, value) 5302 + } 5303 + 5304 + func (_ FfiConverterOptionalSegmentToSign) Write(writer io.Writer, value *SegmentToSign) { 5305 + if value == nil { 5306 + writeInt8(writer, 0) 5307 + } else { 5308 + writeInt8(writer, 1) 5309 + FfiConverterSegmentToSignINSTANCE.Write(writer, *value) 5310 + } 5311 + } 5312 + 5313 + type FfiDestroyerOptionalSegmentToSign struct{} 5314 + 5315 + func (_ FfiDestroyerOptionalSegmentToSign) Destroy(value *SegmentToSign) { 5316 + if value != nil { 5317 + FfiDestroyerSegmentToSign{}.Destroy(*value) 5318 + } 5319 + } 5320 + 5321 + type FfiConverterOptionalStream struct{} 5322 + 5323 + var FfiConverterOptionalStreamINSTANCE = FfiConverterOptionalStream{} 5324 + 5325 + func (c FfiConverterOptionalStream) Lift(rb RustBufferI) *Stream { 5326 + return LiftFromRustBuffer[*Stream](c, rb) 5327 + } 5328 + 5329 + func (_ FfiConverterOptionalStream) Read(reader io.Reader) *Stream { 5330 + if readInt8(reader) == 0 { 5331 + return nil 5332 + } 5333 + temp := FfiConverterStreamINSTANCE.Read(reader) 5334 + return &temp 5335 + } 5336 + 5337 + func (c FfiConverterOptionalStream) Lower(value *Stream) C.RustBuffer { 5338 + return LowerIntoRustBuffer[*Stream](c, value) 5339 + } 5340 + 5341 + func (_ FfiConverterOptionalStream) Write(writer io.Writer, value *Stream) { 5342 + if value == nil { 5343 + writeInt8(writer, 0) 5344 + } else { 5345 + writeInt8(writer, 1) 5346 + FfiConverterStreamINSTANCE.Write(writer, *value) 5347 + } 5348 + } 5349 + 5350 + type FfiDestroyerOptionalStream struct{} 5351 + 5352 + func (_ FfiDestroyerOptionalStream) Destroy(value *Stream) { 5353 + if value != nil { 5354 + FfiDestroyerStream{}.Destroy(*value) 5355 + } 5356 + } 5357 + 5358 type FfiConverterOptionalSubscribeItem struct{} 5359 5360 var FfiConverterOptionalSubscribeItemINSTANCE = FfiConverterOptionalSubscribeItem{} ··· 5584 guard <- struct{}{} 5585 } 5586 5587 + func GetManifestAndCert(data Stream) (string, error) { 5588 _uniffiRV, _uniffiErr := rustCallWithError[SpError](FfiConverterSpError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { 5589 return GoRustBuffer{ 5590 + inner: C.uniffi_iroh_streamplace_fn_func_get_manifest_and_cert(FfiConverterStreamINSTANCE.Lower(data), _uniffiStatus), 5591 + } 5592 + }) 5593 + if _uniffiErr != nil { 5594 + var _uniffiDefaultValue string 5595 + return _uniffiDefaultValue, _uniffiErr 5596 + } else { 5597 + return FfiConverterStringINSTANCE.Lift(_uniffiRV), nil 5598 + } 5599 + } 5600 + 5601 + func GetManifests(data Stream) (string, error) { 5602 + _uniffiRV, _uniffiErr := rustCallWithError[SpError](FfiConverterSpError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { 5603 + return GoRustBuffer{ 5604 + inner: C.uniffi_iroh_streamplace_fn_func_get_manifests(FfiConverterStreamINSTANCE.Lower(data), _uniffiStatus), 5605 } 5606 }) 5607 if _uniffiErr != nil { ··· 5646 } 5647 } 5648 5649 + func Resign(segsToSign ManySegmentsToSign, signedConcatData Stream) error { 5650 + _, _uniffiErr := rustCallWithError[SpError](FfiConverterSpError{}, func(_uniffiStatus *C.RustCallStatus) bool { 5651 + C.uniffi_iroh_streamplace_fn_func_resign(FfiConverterManySegmentsToSignINSTANCE.Lower(segsToSign), FfiConverterStreamINSTANCE.Lower(signedConcatData), _uniffiStatus) 5652 + return false 5653 + }) 5654 + return _uniffiErr.AsError() 5655 + } 5656 + 5657 + func Sign(manifest string, data Stream, certsStr string, gosigner GoSigner) ([]byte, error) { 5658 _uniffiRV, _uniffiErr := rustCallWithError[SpError](FfiConverterSpError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { 5659 return GoRustBuffer{ 5660 + inner: C.uniffi_iroh_streamplace_fn_func_sign(FfiConverterStringINSTANCE.Lower(manifest), FfiConverterStreamINSTANCE.Lower(data), FfiConverterStringINSTANCE.Lower(certsStr), FfiConverterGoSignerINSTANCE.Lower(gosigner), _uniffiStatus), 5661 } 5662 }) 5663 if _uniffiErr != nil { ··· 5666 } else { 5667 return FfiConverterBytesINSTANCE.Lift(_uniffiRV), nil 5668 } 5669 + } 5670 + 5671 + func SignWithIngredients(manifest string, data Stream, certsStr string, ingredients ManyStreams, gosigner GoSigner, output Stream) error { 5672 + _, _uniffiErr := rustCallWithError[SpError](FfiConverterSpError{}, func(_uniffiStatus *C.RustCallStatus) bool { 5673 + C.uniffi_iroh_streamplace_fn_func_sign_with_ingredients(FfiConverterStringINSTANCE.Lower(manifest), FfiConverterStreamINSTANCE.Lower(data), FfiConverterStringINSTANCE.Lower(certsStr), FfiConverterManyStreamsINSTANCE.Lower(ingredients), FfiConverterGoSignerINSTANCE.Lower(gosigner), FfiConverterStreamINSTANCE.Lower(output), _uniffiStatus) 5674 + return false 5675 + }) 5676 + return _uniffiErr.AsError() 5677 } 5678 5679 func SubscribeItemDebug(item SubscribeItem) string {
+397 -2
pkg/iroh/generated/iroh_streamplace/iroh_streamplace.h
··· 406 407 408 #endif 409 #ifndef UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_DATA_HANDLER 410 #define UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_DATA_HANDLER 411 typedef struct UniffiVTableCallbackInterfaceDataHandler { ··· 420 UniffiCallbackInterfaceGoSignerMethod0 sign; 421 UniffiCallbackInterfaceFree uniffiFree; 422 } UniffiVTableCallbackInterfaceGoSigner; 423 424 #endif 425 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_CLONE_DATAHANDLER ··· 548 RustBuffer uniffi_iroh_streamplace_fn_method_gosigner_sign(void* ptr, RustBuffer data, RustCallStatus *out_status 549 ); 550 #endif 551 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_CLONE_NODE 552 #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_CLONE_NODE 553 void* uniffi_iroh_streamplace_fn_clone_node(void* ptr, RustCallStatus *out_status ··· 698 RustBuffer uniffi_iroh_streamplace_fn_method_publickey_uniffi_trait_display(void* ptr, RustCallStatus *out_status 699 ); 700 #endif 701 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_CLONE_SUBSCRIBERESPONSE 702 #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_CLONE_SUBSCRIBERESPONSE 703 void* uniffi_iroh_streamplace_fn_clone_subscriberesponse(void* ptr, RustCallStatus *out_status ··· 735 #endif 736 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_GET_MANIFEST_AND_CERT 737 #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_GET_MANIFEST_AND_CERT 738 - RustBuffer uniffi_iroh_streamplace_fn_func_get_manifest_and_cert(RustBuffer data, RustCallStatus *out_status 739 ); 740 #endif 741 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_INIT_LOGGING ··· 752 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_NODE_ID_FROM_TICKET 753 #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_NODE_ID_FROM_TICKET 754 void* uniffi_iroh_streamplace_fn_func_node_id_from_ticket(RustBuffer ticket_str, RustCallStatus *out_status 755 ); 756 #endif 757 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_SIGN 758 #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_SIGN 759 - RustBuffer uniffi_iroh_streamplace_fn_func_sign(RustBuffer manifest, RustBuffer data, RustBuffer certs, void* gosigner, RustCallStatus *out_status 760 ); 761 #endif 762 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_SUBSCRIBE_ITEM_DEBUG ··· 1050 1051 ); 1052 #endif 1053 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_FUNC_INIT_LOGGING 1054 #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_FUNC_INIT_LOGGING 1055 uint16_t uniffi_iroh_streamplace_checksum_func_init_logging(void ··· 1068 1069 ); 1070 #endif 1071 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_FUNC_SIGN 1072 #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_FUNC_SIGN 1073 uint16_t uniffi_iroh_streamplace_checksum_func_sign(void 1074 1075 ); 1076 #endif ··· 1164 1165 ); 1166 #endif 1167 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_NODE_ADD_TICKETS 1168 #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_NODE_ADD_TICKETS 1169 uint16_t uniffi_iroh_streamplace_checksum_method_node_add_tickets(void ··· 1266 1267 ); 1268 #endif 1269 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_SUBSCRIBERESPONSE_NEXT_RAW 1270 #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_SUBSCRIBERESPONSE_NEXT_RAW 1271 uint16_t uniffi_iroh_streamplace_checksum_method_subscriberesponse_next_raw(void ··· 1331 void iroh_streamplace_cgo_dispatchCallbackInterfaceDataHandlerFree(uint64_t handle); 1332 void iroh_streamplace_cgo_dispatchCallbackInterfaceGoSignerMethod0(uint64_t uniffi_handle, RustBuffer data, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ); 1333 void iroh_streamplace_cgo_dispatchCallbackInterfaceGoSignerFree(uint64_t handle); 1334 1335 void iroh_streamplace_uniffiFutureContinuationCallback(uint64_t, int8_t); 1336 void iroh_streamplace_uniffiFreeGorutine(uint64_t);
··· 406 407 408 #endif 409 + #ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_MANY_SEGMENTS_TO_SIGN_METHOD0 410 + #define UNIFFI_FFIDEF_CALLBACK_INTERFACE_MANY_SEGMENTS_TO_SIGN_METHOD0 411 + typedef void (*UniffiCallbackInterfaceManySegmentsToSignMethod0)(uint64_t uniffi_handle, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ); 412 + 413 + // Making function static works arround: 414 + // https://github.com/golang/go/issues/11263 415 + static void call_UniffiCallbackInterfaceManySegmentsToSignMethod0( 416 + UniffiCallbackInterfaceManySegmentsToSignMethod0 cb, uint64_t uniffi_handle, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ) 417 + { 418 + return cb(uniffi_handle, uniffi_out_return, callStatus ); 419 + } 420 + 421 + 422 + #endif 423 + #ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_MANY_STREAMS_METHOD0 424 + #define UNIFFI_FFIDEF_CALLBACK_INTERFACE_MANY_STREAMS_METHOD0 425 + typedef void (*UniffiCallbackInterfaceManyStreamsMethod0)(uint64_t uniffi_handle, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ); 426 + 427 + // Making function static works arround: 428 + // https://github.com/golang/go/issues/11263 429 + static void call_UniffiCallbackInterfaceManyStreamsMethod0( 430 + UniffiCallbackInterfaceManyStreamsMethod0 cb, uint64_t uniffi_handle, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ) 431 + { 432 + return cb(uniffi_handle, uniffi_out_return, callStatus ); 433 + } 434 + 435 + 436 + #endif 437 + #ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_SEGMENT_TO_SIGN_METHOD0 438 + #define UNIFFI_FFIDEF_CALLBACK_INTERFACE_SEGMENT_TO_SIGN_METHOD0 439 + typedef void (*UniffiCallbackInterfaceSegmentToSignMethod0)(uint64_t uniffi_handle, void** uniffi_out_return, RustCallStatus* callStatus ); 440 + 441 + // Making function static works arround: 442 + // https://github.com/golang/go/issues/11263 443 + static void call_UniffiCallbackInterfaceSegmentToSignMethod0( 444 + UniffiCallbackInterfaceSegmentToSignMethod0 cb, uint64_t uniffi_handle, void** uniffi_out_return, RustCallStatus* callStatus ) 445 + { 446 + return cb(uniffi_handle, uniffi_out_return, callStatus ); 447 + } 448 + 449 + 450 + #endif 451 + #ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_SEGMENT_TO_SIGN_METHOD1 452 + #define UNIFFI_FFIDEF_CALLBACK_INTERFACE_SEGMENT_TO_SIGN_METHOD1 453 + typedef void (*UniffiCallbackInterfaceSegmentToSignMethod1)(uint64_t uniffi_handle, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ); 454 + 455 + // Making function static works arround: 456 + // https://github.com/golang/go/issues/11263 457 + static void call_UniffiCallbackInterfaceSegmentToSignMethod1( 458 + UniffiCallbackInterfaceSegmentToSignMethod1 cb, uint64_t uniffi_handle, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ) 459 + { 460 + return cb(uniffi_handle, uniffi_out_return, callStatus ); 461 + } 462 + 463 + 464 + #endif 465 + #ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_SEGMENT_TO_SIGN_METHOD2 466 + #define UNIFFI_FFIDEF_CALLBACK_INTERFACE_SEGMENT_TO_SIGN_METHOD2 467 + typedef void (*UniffiCallbackInterfaceSegmentToSignMethod2)(uint64_t uniffi_handle, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ); 468 + 469 + // Making function static works arround: 470 + // https://github.com/golang/go/issues/11263 471 + static void call_UniffiCallbackInterfaceSegmentToSignMethod2( 472 + UniffiCallbackInterfaceSegmentToSignMethod2 cb, uint64_t uniffi_handle, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ) 473 + { 474 + return cb(uniffi_handle, uniffi_out_return, callStatus ); 475 + } 476 + 477 + 478 + #endif 479 + #ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_SEGMENT_TO_SIGN_METHOD3 480 + #define UNIFFI_FFIDEF_CALLBACK_INTERFACE_SEGMENT_TO_SIGN_METHOD3 481 + typedef void (*UniffiCallbackInterfaceSegmentToSignMethod3)(uint64_t uniffi_handle, void** uniffi_out_return, RustCallStatus* callStatus ); 482 + 483 + // Making function static works arround: 484 + // https://github.com/golang/go/issues/11263 485 + static void call_UniffiCallbackInterfaceSegmentToSignMethod3( 486 + UniffiCallbackInterfaceSegmentToSignMethod3 cb, uint64_t uniffi_handle, void** uniffi_out_return, RustCallStatus* callStatus ) 487 + { 488 + return cb(uniffi_handle, uniffi_out_return, callStatus ); 489 + } 490 + 491 + 492 + #endif 493 + #ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_SEGMENT_TO_SIGN_METHOD4 494 + #define UNIFFI_FFIDEF_CALLBACK_INTERFACE_SEGMENT_TO_SIGN_METHOD4 495 + typedef void (*UniffiCallbackInterfaceSegmentToSignMethod4)(uint64_t uniffi_handle, void* uniffi_out_return, RustCallStatus* callStatus ); 496 + 497 + // Making function static works arround: 498 + // https://github.com/golang/go/issues/11263 499 + static void call_UniffiCallbackInterfaceSegmentToSignMethod4( 500 + UniffiCallbackInterfaceSegmentToSignMethod4 cb, uint64_t uniffi_handle, void* uniffi_out_return, RustCallStatus* callStatus ) 501 + { 502 + return cb(uniffi_handle, uniffi_out_return, callStatus ); 503 + } 504 + 505 + 506 + #endif 507 + #ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_STREAM_METHOD0 508 + #define UNIFFI_FFIDEF_CALLBACK_INTERFACE_STREAM_METHOD0 509 + typedef void (*UniffiCallbackInterfaceStreamMethod0)(uint64_t uniffi_handle, uint64_t length, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ); 510 + 511 + // Making function static works arround: 512 + // https://github.com/golang/go/issues/11263 513 + static void call_UniffiCallbackInterfaceStreamMethod0( 514 + UniffiCallbackInterfaceStreamMethod0 cb, uint64_t uniffi_handle, uint64_t length, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ) 515 + { 516 + return cb(uniffi_handle, length, uniffi_out_return, callStatus ); 517 + } 518 + 519 + 520 + #endif 521 + #ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_STREAM_METHOD1 522 + #define UNIFFI_FFIDEF_CALLBACK_INTERFACE_STREAM_METHOD1 523 + typedef void (*UniffiCallbackInterfaceStreamMethod1)(uint64_t uniffi_handle, int64_t pos, uint64_t mode, uint64_t* uniffi_out_return, RustCallStatus* callStatus ); 524 + 525 + // Making function static works arround: 526 + // https://github.com/golang/go/issues/11263 527 + static void call_UniffiCallbackInterfaceStreamMethod1( 528 + UniffiCallbackInterfaceStreamMethod1 cb, uint64_t uniffi_handle, int64_t pos, uint64_t mode, uint64_t* uniffi_out_return, RustCallStatus* callStatus ) 529 + { 530 + return cb(uniffi_handle, pos, mode, uniffi_out_return, callStatus ); 531 + } 532 + 533 + 534 + #endif 535 + #ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_STREAM_METHOD2 536 + #define UNIFFI_FFIDEF_CALLBACK_INTERFACE_STREAM_METHOD2 537 + typedef void (*UniffiCallbackInterfaceStreamMethod2)(uint64_t uniffi_handle, RustBuffer data, uint64_t* uniffi_out_return, RustCallStatus* callStatus ); 538 + 539 + // Making function static works arround: 540 + // https://github.com/golang/go/issues/11263 541 + static void call_UniffiCallbackInterfaceStreamMethod2( 542 + UniffiCallbackInterfaceStreamMethod2 cb, uint64_t uniffi_handle, RustBuffer data, uint64_t* uniffi_out_return, RustCallStatus* callStatus ) 543 + { 544 + return cb(uniffi_handle, data, uniffi_out_return, callStatus ); 545 + } 546 + 547 + 548 + #endif 549 #ifndef UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_DATA_HANDLER 550 #define UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_DATA_HANDLER 551 typedef struct UniffiVTableCallbackInterfaceDataHandler { ··· 560 UniffiCallbackInterfaceGoSignerMethod0 sign; 561 UniffiCallbackInterfaceFree uniffiFree; 562 } UniffiVTableCallbackInterfaceGoSigner; 563 + 564 + #endif 565 + #ifndef UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_MANY_SEGMENTS_TO_SIGN 566 + #define UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_MANY_SEGMENTS_TO_SIGN 567 + typedef struct UniffiVTableCallbackInterfaceManySegmentsToSign { 568 + UniffiCallbackInterfaceManySegmentsToSignMethod0 next; 569 + UniffiCallbackInterfaceFree uniffiFree; 570 + } UniffiVTableCallbackInterfaceManySegmentsToSign; 571 + 572 + #endif 573 + #ifndef UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_MANY_STREAMS 574 + #define UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_MANY_STREAMS 575 + typedef struct UniffiVTableCallbackInterfaceManyStreams { 576 + UniffiCallbackInterfaceManyStreamsMethod0 next; 577 + UniffiCallbackInterfaceFree uniffiFree; 578 + } UniffiVTableCallbackInterfaceManyStreams; 579 + 580 + #endif 581 + #ifndef UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_SEGMENT_TO_SIGN 582 + #define UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_SEGMENT_TO_SIGN 583 + typedef struct UniffiVTableCallbackInterfaceSegmentToSign { 584 + UniffiCallbackInterfaceSegmentToSignMethod0 unsignedSegStream; 585 + UniffiCallbackInterfaceSegmentToSignMethod1 manifestId; 586 + UniffiCallbackInterfaceSegmentToSignMethod2 cert; 587 + UniffiCallbackInterfaceSegmentToSignMethod3 outputSegStream; 588 + UniffiCallbackInterfaceSegmentToSignMethod4 close; 589 + UniffiCallbackInterfaceFree uniffiFree; 590 + } UniffiVTableCallbackInterfaceSegmentToSign; 591 + 592 + #endif 593 + #ifndef UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_STREAM 594 + #define UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_STREAM 595 + typedef struct UniffiVTableCallbackInterfaceStream { 596 + UniffiCallbackInterfaceStreamMethod0 readStream; 597 + UniffiCallbackInterfaceStreamMethod1 seekStream; 598 + UniffiCallbackInterfaceStreamMethod2 writeStream; 599 + UniffiCallbackInterfaceFree uniffiFree; 600 + } UniffiVTableCallbackInterfaceStream; 601 602 #endif 603 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_CLONE_DATAHANDLER ··· 726 RustBuffer uniffi_iroh_streamplace_fn_method_gosigner_sign(void* ptr, RustBuffer data, RustCallStatus *out_status 727 ); 728 #endif 729 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_CLONE_MANYSEGMENTSTOSIGN 730 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_CLONE_MANYSEGMENTSTOSIGN 731 + void* uniffi_iroh_streamplace_fn_clone_manysegmentstosign(void* ptr, RustCallStatus *out_status 732 + ); 733 + #endif 734 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FREE_MANYSEGMENTSTOSIGN 735 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FREE_MANYSEGMENTSTOSIGN 736 + void uniffi_iroh_streamplace_fn_free_manysegmentstosign(void* ptr, RustCallStatus *out_status 737 + ); 738 + #endif 739 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_INIT_CALLBACK_VTABLE_MANYSEGMENTSTOSIGN 740 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_INIT_CALLBACK_VTABLE_MANYSEGMENTSTOSIGN 741 + void uniffi_iroh_streamplace_fn_init_callback_vtable_manysegmentstosign(UniffiVTableCallbackInterfaceManySegmentsToSign* vtable 742 + ); 743 + #endif 744 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_MANYSEGMENTSTOSIGN_NEXT 745 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_MANYSEGMENTSTOSIGN_NEXT 746 + RustBuffer uniffi_iroh_streamplace_fn_method_manysegmentstosign_next(void* ptr, RustCallStatus *out_status 747 + ); 748 + #endif 749 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_CLONE_MANYSTREAMS 750 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_CLONE_MANYSTREAMS 751 + void* uniffi_iroh_streamplace_fn_clone_manystreams(void* ptr, RustCallStatus *out_status 752 + ); 753 + #endif 754 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FREE_MANYSTREAMS 755 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FREE_MANYSTREAMS 756 + void uniffi_iroh_streamplace_fn_free_manystreams(void* ptr, RustCallStatus *out_status 757 + ); 758 + #endif 759 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_INIT_CALLBACK_VTABLE_MANYSTREAMS 760 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_INIT_CALLBACK_VTABLE_MANYSTREAMS 761 + void uniffi_iroh_streamplace_fn_init_callback_vtable_manystreams(UniffiVTableCallbackInterfaceManyStreams* vtable 762 + ); 763 + #endif 764 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_MANYSTREAMS_NEXT 765 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_MANYSTREAMS_NEXT 766 + RustBuffer uniffi_iroh_streamplace_fn_method_manystreams_next(void* ptr, RustCallStatus *out_status 767 + ); 768 + #endif 769 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_CLONE_NODE 770 #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_CLONE_NODE 771 void* uniffi_iroh_streamplace_fn_clone_node(void* ptr, RustCallStatus *out_status ··· 916 RustBuffer uniffi_iroh_streamplace_fn_method_publickey_uniffi_trait_display(void* ptr, RustCallStatus *out_status 917 ); 918 #endif 919 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_CLONE_SEGMENTTOSIGN 920 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_CLONE_SEGMENTTOSIGN 921 + void* uniffi_iroh_streamplace_fn_clone_segmenttosign(void* ptr, RustCallStatus *out_status 922 + ); 923 + #endif 924 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FREE_SEGMENTTOSIGN 925 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FREE_SEGMENTTOSIGN 926 + void uniffi_iroh_streamplace_fn_free_segmenttosign(void* ptr, RustCallStatus *out_status 927 + ); 928 + #endif 929 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_INIT_CALLBACK_VTABLE_SEGMENTTOSIGN 930 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_INIT_CALLBACK_VTABLE_SEGMENTTOSIGN 931 + void uniffi_iroh_streamplace_fn_init_callback_vtable_segmenttosign(UniffiVTableCallbackInterfaceSegmentToSign* vtable 932 + ); 933 + #endif 934 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_SEGMENTTOSIGN_UNSIGNED_SEG_STREAM 935 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_SEGMENTTOSIGN_UNSIGNED_SEG_STREAM 936 + void* uniffi_iroh_streamplace_fn_method_segmenttosign_unsigned_seg_stream(void* ptr, RustCallStatus *out_status 937 + ); 938 + #endif 939 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_SEGMENTTOSIGN_MANIFEST_ID 940 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_SEGMENTTOSIGN_MANIFEST_ID 941 + RustBuffer uniffi_iroh_streamplace_fn_method_segmenttosign_manifest_id(void* ptr, RustCallStatus *out_status 942 + ); 943 + #endif 944 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_SEGMENTTOSIGN_CERT 945 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_SEGMENTTOSIGN_CERT 946 + RustBuffer uniffi_iroh_streamplace_fn_method_segmenttosign_cert(void* ptr, RustCallStatus *out_status 947 + ); 948 + #endif 949 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_SEGMENTTOSIGN_OUTPUT_SEG_STREAM 950 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_SEGMENTTOSIGN_OUTPUT_SEG_STREAM 951 + void* uniffi_iroh_streamplace_fn_method_segmenttosign_output_seg_stream(void* ptr, RustCallStatus *out_status 952 + ); 953 + #endif 954 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_SEGMENTTOSIGN_CLOSE 955 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_SEGMENTTOSIGN_CLOSE 956 + void uniffi_iroh_streamplace_fn_method_segmenttosign_close(void* ptr, RustCallStatus *out_status 957 + ); 958 + #endif 959 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_CLONE_STREAM 960 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_CLONE_STREAM 961 + void* uniffi_iroh_streamplace_fn_clone_stream(void* ptr, RustCallStatus *out_status 962 + ); 963 + #endif 964 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FREE_STREAM 965 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FREE_STREAM 966 + void uniffi_iroh_streamplace_fn_free_stream(void* ptr, RustCallStatus *out_status 967 + ); 968 + #endif 969 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_INIT_CALLBACK_VTABLE_STREAM 970 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_INIT_CALLBACK_VTABLE_STREAM 971 + void uniffi_iroh_streamplace_fn_init_callback_vtable_stream(UniffiVTableCallbackInterfaceStream* vtable 972 + ); 973 + #endif 974 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_STREAM_READ_STREAM 975 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_STREAM_READ_STREAM 976 + RustBuffer uniffi_iroh_streamplace_fn_method_stream_read_stream(void* ptr, uint64_t length, RustCallStatus *out_status 977 + ); 978 + #endif 979 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_STREAM_SEEK_STREAM 980 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_STREAM_SEEK_STREAM 981 + uint64_t uniffi_iroh_streamplace_fn_method_stream_seek_stream(void* ptr, int64_t pos, uint64_t mode, RustCallStatus *out_status 982 + ); 983 + #endif 984 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_STREAM_WRITE_STREAM 985 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_METHOD_STREAM_WRITE_STREAM 986 + uint64_t uniffi_iroh_streamplace_fn_method_stream_write_stream(void* ptr, RustBuffer data, RustCallStatus *out_status 987 + ); 988 + #endif 989 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_CLONE_SUBSCRIBERESPONSE 990 #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_CLONE_SUBSCRIBERESPONSE 991 void* uniffi_iroh_streamplace_fn_clone_subscriberesponse(void* ptr, RustCallStatus *out_status ··· 1023 #endif 1024 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_GET_MANIFEST_AND_CERT 1025 #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_GET_MANIFEST_AND_CERT 1026 + RustBuffer uniffi_iroh_streamplace_fn_func_get_manifest_and_cert(void* data, RustCallStatus *out_status 1027 + ); 1028 + #endif 1029 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_GET_MANIFESTS 1030 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_GET_MANIFESTS 1031 + RustBuffer uniffi_iroh_streamplace_fn_func_get_manifests(void* data, RustCallStatus *out_status 1032 ); 1033 #endif 1034 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_INIT_LOGGING ··· 1045 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_NODE_ID_FROM_TICKET 1046 #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_NODE_ID_FROM_TICKET 1047 void* uniffi_iroh_streamplace_fn_func_node_id_from_ticket(RustBuffer ticket_str, RustCallStatus *out_status 1048 + ); 1049 + #endif 1050 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_RESIGN 1051 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_RESIGN 1052 + void uniffi_iroh_streamplace_fn_func_resign(void* segs_to_sign, void* signed_concat_data, RustCallStatus *out_status 1053 ); 1054 #endif 1055 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_SIGN 1056 #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_SIGN 1057 + RustBuffer uniffi_iroh_streamplace_fn_func_sign(RustBuffer manifest, void* data, RustBuffer certs_str, void* gosigner, RustCallStatus *out_status 1058 + ); 1059 + #endif 1060 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_SIGN_WITH_INGREDIENTS 1061 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_SIGN_WITH_INGREDIENTS 1062 + void uniffi_iroh_streamplace_fn_func_sign_with_ingredients(RustBuffer manifest, void* data, RustBuffer certs_str, void* ingredients, void* gosigner, void* output, RustCallStatus *out_status 1063 ); 1064 #endif 1065 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_FN_FUNC_SUBSCRIBE_ITEM_DEBUG ··· 1353 1354 ); 1355 #endif 1356 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_FUNC_GET_MANIFESTS 1357 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_FUNC_GET_MANIFESTS 1358 + uint16_t uniffi_iroh_streamplace_checksum_func_get_manifests(void 1359 + 1360 + ); 1361 + #endif 1362 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_FUNC_INIT_LOGGING 1363 #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_FUNC_INIT_LOGGING 1364 uint16_t uniffi_iroh_streamplace_checksum_func_init_logging(void ··· 1377 1378 ); 1379 #endif 1380 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_FUNC_RESIGN 1381 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_FUNC_RESIGN 1382 + uint16_t uniffi_iroh_streamplace_checksum_func_resign(void 1383 + 1384 + ); 1385 + #endif 1386 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_FUNC_SIGN 1387 #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_FUNC_SIGN 1388 uint16_t uniffi_iroh_streamplace_checksum_func_sign(void 1389 + 1390 + ); 1391 + #endif 1392 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_FUNC_SIGN_WITH_INGREDIENTS 1393 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_FUNC_SIGN_WITH_INGREDIENTS 1394 + uint16_t uniffi_iroh_streamplace_checksum_func_sign_with_ingredients(void 1395 1396 ); 1397 #endif ··· 1485 1486 ); 1487 #endif 1488 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_MANYSEGMENTSTOSIGN_NEXT 1489 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_MANYSEGMENTSTOSIGN_NEXT 1490 + uint16_t uniffi_iroh_streamplace_checksum_method_manysegmentstosign_next(void 1491 + 1492 + ); 1493 + #endif 1494 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_MANYSTREAMS_NEXT 1495 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_MANYSTREAMS_NEXT 1496 + uint16_t uniffi_iroh_streamplace_checksum_method_manystreams_next(void 1497 + 1498 + ); 1499 + #endif 1500 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_NODE_ADD_TICKETS 1501 #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_NODE_ADD_TICKETS 1502 uint16_t uniffi_iroh_streamplace_checksum_method_node_add_tickets(void ··· 1599 1600 ); 1601 #endif 1602 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_SEGMENTTOSIGN_UNSIGNED_SEG_STREAM 1603 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_SEGMENTTOSIGN_UNSIGNED_SEG_STREAM 1604 + uint16_t uniffi_iroh_streamplace_checksum_method_segmenttosign_unsigned_seg_stream(void 1605 + 1606 + ); 1607 + #endif 1608 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_SEGMENTTOSIGN_MANIFEST_ID 1609 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_SEGMENTTOSIGN_MANIFEST_ID 1610 + uint16_t uniffi_iroh_streamplace_checksum_method_segmenttosign_manifest_id(void 1611 + 1612 + ); 1613 + #endif 1614 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_SEGMENTTOSIGN_CERT 1615 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_SEGMENTTOSIGN_CERT 1616 + uint16_t uniffi_iroh_streamplace_checksum_method_segmenttosign_cert(void 1617 + 1618 + ); 1619 + #endif 1620 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_SEGMENTTOSIGN_OUTPUT_SEG_STREAM 1621 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_SEGMENTTOSIGN_OUTPUT_SEG_STREAM 1622 + uint16_t uniffi_iroh_streamplace_checksum_method_segmenttosign_output_seg_stream(void 1623 + 1624 + ); 1625 + #endif 1626 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_SEGMENTTOSIGN_CLOSE 1627 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_SEGMENTTOSIGN_CLOSE 1628 + uint16_t uniffi_iroh_streamplace_checksum_method_segmenttosign_close(void 1629 + 1630 + ); 1631 + #endif 1632 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_STREAM_READ_STREAM 1633 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_STREAM_READ_STREAM 1634 + uint16_t uniffi_iroh_streamplace_checksum_method_stream_read_stream(void 1635 + 1636 + ); 1637 + #endif 1638 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_STREAM_SEEK_STREAM 1639 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_STREAM_SEEK_STREAM 1640 + uint16_t uniffi_iroh_streamplace_checksum_method_stream_seek_stream(void 1641 + 1642 + ); 1643 + #endif 1644 + #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_STREAM_WRITE_STREAM 1645 + #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_STREAM_WRITE_STREAM 1646 + uint16_t uniffi_iroh_streamplace_checksum_method_stream_write_stream(void 1647 + 1648 + ); 1649 + #endif 1650 #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_SUBSCRIBERESPONSE_NEXT_RAW 1651 #define UNIFFI_FFIDEF_UNIFFI_IROH_STREAMPLACE_CHECKSUM_METHOD_SUBSCRIBERESPONSE_NEXT_RAW 1652 uint16_t uniffi_iroh_streamplace_checksum_method_subscriberesponse_next_raw(void ··· 1712 void iroh_streamplace_cgo_dispatchCallbackInterfaceDataHandlerFree(uint64_t handle); 1713 void iroh_streamplace_cgo_dispatchCallbackInterfaceGoSignerMethod0(uint64_t uniffi_handle, RustBuffer data, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ); 1714 void iroh_streamplace_cgo_dispatchCallbackInterfaceGoSignerFree(uint64_t handle); 1715 + void iroh_streamplace_cgo_dispatchCallbackInterfaceManySegmentsToSignMethod0(uint64_t uniffi_handle, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ); 1716 + void iroh_streamplace_cgo_dispatchCallbackInterfaceManySegmentsToSignFree(uint64_t handle); 1717 + void iroh_streamplace_cgo_dispatchCallbackInterfaceManyStreamsMethod0(uint64_t uniffi_handle, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ); 1718 + void iroh_streamplace_cgo_dispatchCallbackInterfaceManyStreamsFree(uint64_t handle); 1719 + void iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod0(uint64_t uniffi_handle, void** uniffi_out_return, RustCallStatus* callStatus ); 1720 + void iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod1(uint64_t uniffi_handle, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ); 1721 + void iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod2(uint64_t uniffi_handle, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ); 1722 + void iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod3(uint64_t uniffi_handle, void** uniffi_out_return, RustCallStatus* callStatus ); 1723 + void iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignMethod4(uint64_t uniffi_handle, void* uniffi_out_return, RustCallStatus* callStatus ); 1724 + void iroh_streamplace_cgo_dispatchCallbackInterfaceSegmentToSignFree(uint64_t handle); 1725 + void iroh_streamplace_cgo_dispatchCallbackInterfaceStreamMethod0(uint64_t uniffi_handle, uint64_t length, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ); 1726 + void iroh_streamplace_cgo_dispatchCallbackInterfaceStreamMethod1(uint64_t uniffi_handle, int64_t pos, uint64_t mode, uint64_t* uniffi_out_return, RustCallStatus* callStatus ); 1727 + void iroh_streamplace_cgo_dispatchCallbackInterfaceStreamMethod2(uint64_t uniffi_handle, RustBuffer data, uint64_t* uniffi_out_return, RustCallStatus* callStatus ); 1728 + void iroh_streamplace_cgo_dispatchCallbackInterfaceStreamFree(uint64_t handle); 1729 1730 void iroh_streamplace_uniffiFutureContinuationCallback(uint64_t, int8_t); 1731 void iroh_streamplace_uniffiFreeGorutine(uint64_t);
+2 -2
pkg/log/log.go
··· 263 } 264 265 func Debug(ctx context.Context, message string, args ...any) { 266 - V(debugLogLevel).log(ctx, message, slog.Debug, args...) 267 } 268 269 func Trace(ctx context.Context, message string, args ...any) { 270 - V(traceLogLevel).log(ctx, message, slog.Debug, args...) 271 } 272 273 // returns true if we are at least the given level
··· 263 } 264 265 func Debug(ctx context.Context, message string, args ...any) { 266 + V(debugLogLevel).log(ctx, message, slog.Info, args...) 267 } 268 269 func Trace(ctx context.Context, message string, args ...any) { 270 + V(traceLogLevel).log(ctx, message, slog.Info, args...) 271 } 272 273 // returns true if we are at least the given level
+47 -53
pkg/media/audio_smear.go
··· 5 "context" 6 "fmt" 7 "io" 8 - "os" 9 "strings" 10 "time" 11 12 "github.com/go-gst/go-gst/gst" 13 "github.com/go-gst/go-gst/gst/app" 14 "github.com/google/uuid" 15 "stream.place/streamplace/pkg/log" 16 ) 17 ··· 28 VideoCaps string 29 } 30 31 - func SmearAudioTimestamps(ctx context.Context, input io.Reader, output io.Writer) error { 32 bs, err := io.ReadAll(input) 33 if err != nil { 34 return err 35 } 36 seg, err := ToBuffers(ctx, bytes.NewReader(bs)) 37 if err != nil { 38 - // Write the input bytes to a file for debugging 39 - debugFile := fmt.Sprintf("audio_smear_debug_%s.mp4", uuid.New().String()) 40 - err = os.WriteFile(debugFile, bs, 0644) 41 - if err != nil { 42 - log.Log(ctx, "failed to write debug file", "error", err, "path", debugFile) 43 - } else { 44 - log.Log(ctx, "wrote debug file", "path", debugFile) 45 - } 46 return err 47 } 48 49 - err = seg.Normalize(ctx) 50 - if err != nil { 51 - return err 52 } 53 54 return JoinAudioVideo(ctx, seg, output) ··· 90 } 91 92 func ToBuffers(ctx context.Context, input io.Reader) (*SegmentData, error) { 93 ctx = log.WithLogValues(ctx, "func", "SplitAudioVideo") 94 95 pipelineSlice := []string{ 96 "appsrc name=mp4src ! qtdemux name=demux", 97 - "demux.video_0 ! queue ! h264parse name=videoparse disable-passthrough=true config-interval=-1 ! appsink sync=false name=videoappsink", 98 "demux.audio_0 ! queue ! opusparse name=audioparse ! appsink sync=false name=audioappsink", 99 } 100 101 - ctx, cancel := context.WithCancel(ctx) 102 - defer cancel() 103 - 104 pipeline, err := gst.NewPipelineFromString(strings.Join(pipelineSlice, "\n")) 105 if err != nil { 106 return nil, fmt.Errorf("failed to create GStreamer pipeline: %w", err) 107 } 108 - 109 - errCh := make(chan error) 110 - go func() { 111 - err := HandleBusMessages(ctx, pipeline) 112 - cancel() 113 - errCh <- err 114 - close(errCh) 115 - }() 116 - 117 - defer func() { 118 - err := <-errCh 119 - if err != nil { 120 - log.Error(ctx, "bus handler error", "error", err) 121 - } 122 - err = pipeline.BlockSetState(gst.StateNull) 123 - if err != nil { 124 - log.Error(ctx, "failed to set pipeline to null state", "error", err) 125 - } 126 - }() 127 128 mp4src, err := pipeline.GetElementByName("mp4src") 129 if err != nil { ··· 230 return gst.FlowOK 231 }, 232 }) 233 234 if err := pipeline.SetState(gst.StatePlaying); err != nil { 235 return nil, fmt.Errorf("failed to set pipeline state: %w", err) 236 } 237 238 - <-ctx.Done() 239 240 - return &seg, <-errCh 241 } 242 243 func JoinAudioVideo(ctx context.Context, seg *SegmentData, output io.Writer) error { ··· 261 errCh := make(chan error) 262 go func() { 263 err := HandleBusMessages(ctx, pipeline) 264 - cancel() 265 errCh <- err 266 - close(errCh) 267 - }() 268 - 269 - defer func() { 270 - err := <-errCh 271 - if err != nil { 272 - log.Error(ctx, "bus handler error", "error", err) 273 - } 274 - err = pipeline.BlockSetState(gst.StateNull) 275 - if err != nil { 276 - log.Error(ctx, "failed to set pipeline to null state", "error", err) 277 - } 278 }() 279 280 videoSrcElem, err := pipeline.GetElementByName("videosrc") ··· 340 NewSampleFunc: WriterNewSample(ctx, output), 341 }) 342 343 if err := pipeline.SetState(gst.StatePlaying); err != nil { 344 return fmt.Errorf("failed to set pipeline state: %w", err) 345 } 346 - 347 - <-ctx.Done() 348 349 return <-errCh 350 }
··· 5 "context" 6 "fmt" 7 "io" 8 "strings" 9 "time" 10 11 "github.com/go-gst/go-gst/gst" 12 "github.com/go-gst/go-gst/gst/app" 13 "github.com/google/uuid" 14 + "stream.place/streamplace/pkg/config" 15 "stream.place/streamplace/pkg/log" 16 ) 17 ··· 28 VideoCaps string 29 } 30 31 + func RewriteAudioTimestamps(ctx context.Context, cli *config.CLI, input io.Reader, output io.Writer, doSmear bool) error { 32 bs, err := io.ReadAll(input) 33 if err != nil { 34 return err 35 } 36 seg, err := ToBuffers(ctx, bytes.NewReader(bs)) 37 if err != nil { 38 + cli.DumpDebugSegment(ctx, "audio_smear_input", bytes.NewReader(bs)) 39 return err 40 } 41 42 + if doSmear { 43 + err = seg.Normalize(ctx) 44 + if err != nil { 45 + return err 46 + } 47 } 48 49 return JoinAudioVideo(ctx, seg, output) ··· 85 } 86 87 func ToBuffers(ctx context.Context, input io.Reader) (*SegmentData, error) { 88 + 89 + ctx, cancel := context.WithCancel(ctx) 90 + defer cancel() 91 ctx = log.WithLogValues(ctx, "func", "SplitAudioVideo") 92 93 pipelineSlice := []string{ 94 "appsrc name=mp4src ! qtdemux name=demux", 95 + "demux.video_0 ! queue ! appsink sync=false name=videoappsink", 96 "demux.audio_0 ! queue ! opusparse name=audioparse ! appsink sync=false name=audioappsink", 97 } 98 99 pipeline, err := gst.NewPipelineFromString(strings.Join(pipelineSlice, "\n")) 100 if err != nil { 101 return nil, fmt.Errorf("failed to create GStreamer pipeline: %w", err) 102 } 103 104 mp4src, err := pipeline.GetElementByName("mp4src") 105 if err != nil { ··· 206 return gst.FlowOK 207 }, 208 }) 209 + errCh := make(chan error) 210 + go func() { 211 + err := HandleBusMessages(ctx, pipeline) 212 + errCh <- err 213 + }() 214 + 215 + defer func() { 216 + if err != nil { 217 + log.Error(ctx, "bus handler error", "error", err) 218 + } 219 + err = pipeline.SetState(gst.StateNull) 220 + if err != nil { 221 + log.Error(ctx, "failed to set pipeline to null state", "error", err) 222 + } 223 + }() 224 225 if err := pipeline.SetState(gst.StatePlaying); err != nil { 226 return nil, fmt.Errorf("failed to set pipeline state: %w", err) 227 } 228 229 + pipelineErr := <-errCh 230 231 + if pipelineErr != nil { 232 + return nil, fmt.Errorf("pipeline error: %w", pipelineErr) 233 + } 234 + 235 + if len(seg.Video) == 0 { 236 + return nil, fmt.Errorf("no video segments when rewriting audio") 237 + } 238 + if len(seg.Audio) == 0 { 239 + return nil, fmt.Errorf("no audio segments when rewriting audio") 240 + } 241 + 242 + return &seg, nil 243 } 244 245 func JoinAudioVideo(ctx context.Context, seg *SegmentData, output io.Writer) error { ··· 263 errCh := make(chan error) 264 go func() { 265 err := HandleBusMessages(ctx, pipeline) 266 errCh <- err 267 }() 268 269 videoSrcElem, err := pipeline.GetElementByName("videosrc") ··· 329 NewSampleFunc: WriterNewSample(ctx, output), 330 }) 331 332 + defer func() { 333 + err = pipeline.SetState(gst.StateNull) 334 + if err != nil { 335 + log.Error(ctx, "failed to set pipeline to null state", "error", err) 336 + } 337 + }() 338 + 339 if err := pipeline.SetState(gst.StatePlaying); err != nil { 340 return fmt.Errorf("failed to set pipeline state: %w", err) 341 } 342 343 return <-errCh 344 }
+1 -1
pkg/media/audio_smear_test.go
··· 61 return err 62 } 63 64 - require.Equal(t, 1191255, buf.Len()) 65 66 // // Write audio and video buffers to temporary files for further analysis 67 // tempDir := t.TempDir()
··· 61 return err 62 } 63 64 + require.Equal(t, 1191212, buf.Len()) 65 66 // // Write audio and video buffers to temporary files for further analysis 67 // tempDir := t.TempDir()
+36 -37
pkg/media/clip.go pkg/media/segment_combine.go
··· 1 package media 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 - "os" 8 "strings" 9 10 "github.com/go-gst/go-gst/gst" 11 "github.com/go-gst/go-gst/gst/app" 12 - "github.com/google/uuid" 13 "stream.place/streamplace/pkg/bus" 14 "stream.place/streamplace/pkg/log" 15 ) 16 17 - func readFile(ctx context.Context, source string) (*bus.Seg, error) { 18 - fd, err := os.Open(source) 19 if err != nil { 20 - return nil, fmt.Errorf("failed to open source file: %w", err) 21 } 22 - defer fd.Close() 23 - bs, err := io.ReadAll(fd) 24 if err != nil { 25 - return nil, fmt.Errorf("failed to read source file: %w", err) 26 } 27 - seg := &bus.Seg{ 28 - Filepath: source, 29 - Data: bs, 30 } 31 - return seg, nil 32 } 33 34 - // This function remains in scope for the duration of a single users' playback 35 - func Clip(ctx context.Context, sources []string, w io.Writer) error { 36 - uu, err := uuid.NewV7() 37 - if err != nil { 38 - return err 39 - } 40 - ctx = log.WithLogValues(ctx, "webrtcID", uu.String()) 41 - ctx = log.WithLogValues(ctx, "mediafunc", "Clip") 42 ctx, cancel := context.WithCancel(ctx) 43 defer cancel() 44 45 pipelineSlice := []string{ 46 - "mp4mux faststart=true name=muxer ! appsink sync=false name=mp4sink", 47 - "h264parse name=videoparse ! h264timestamper ! muxer.video_0", 48 - "opusparse name=audioparse ! muxer.audio_0", 49 } 50 51 pipeline, err := gst.NewPipelineFromString(strings.Join(pipelineSlice, "\n")) ··· 56 segCh := make(chan *bus.Seg) 57 go func() { 58 for _, source := range sources { 59 - log.Log(ctx, "reading file", "source", source) 60 - seg, err := readFile(ctx, source) 61 if err != nil { 62 err = fmt.Errorf("failed to read file: %w", err) 63 pipeline.Error(err.Error(), err) 64 return 65 } 66 - 67 - segCh <- seg 68 } 69 close(segCh) 70 }() 71 72 - concatBin, err := ConcatBin(ctx, segCh) 73 if err != nil { 74 return fmt.Errorf("failed to create concat bin: %w", err) 75 } ··· 90 } 91 92 // Get the videoparse and audioparse elements from the pipeline 93 - videoParse, err := pipeline.GetElementByName("videoparse") 94 if err != nil { 95 return fmt.Errorf("failed to get video parse element: %w", err) 96 } ··· 101 } 102 103 // Link the concat bin pads to the parse element sink pads 104 - linked := videoPad.Link(videoParse.GetStaticPad("sink")) 105 if linked != gst.PadLinkOK { 106 return fmt.Errorf("failed to link video pad to video parse element: %v", linked) 107 } ··· 117 return fmt.Errorf("failed to get mp4sink element: %w", err) 118 } 119 120 - eos := make(chan struct{}) 121 - 122 appSink := app.SinkFromElement(mp4Sink) 123 appSink.SetCallbacks(&app.SinkCallbacks{ 124 NewSampleFunc: WriterNewSample(ctx, w), 125 - EOSFunc: func(sink *app.Sink) { 126 - close(eos) 127 - }, 128 }) 129 130 // Start the pipeline ··· 141 142 // Handle bus messages 143 err = HandleBusMessages(ctx, pipeline) 144 - 145 - <-eos 146 147 if err != nil { 148 return fmt.Errorf("pipeline error: %w", err)
··· 1 package media 2 3 import ( 4 + "bytes" 5 "context" 6 "fmt" 7 "io" 8 "strings" 9 10 "github.com/go-gst/go-gst/gst" 11 "github.com/go-gst/go-gst/gst/app" 12 + "stream.place/streamplace/pkg/aqio" 13 "stream.place/streamplace/pkg/bus" 14 "stream.place/streamplace/pkg/log" 15 ) 16 17 + // CombineSegments combines a list of segments into a single segment that maintains all of the manifests 18 + func CombineSegments(ctx context.Context, inputFds []io.ReadSeeker, ms MediaSigner, output io.ReadWriteSeeker) error { 19 + rws := aqio.NewReadWriteSeeker([]byte{}) 20 + err := CombineSegmentsUnsigned(ctx, inputFds, rws, true) 21 if err != nil { 22 + return err 23 + } 24 + // rewind all the inputs for the signer 25 + for _, fd := range inputFds { 26 + _, err := fd.Seek(0, io.SeekStart) 27 + if err != nil { 28 + return err 29 + } 30 } 31 + bs, err := rws.Bytes() 32 if err != nil { 33 + return err 34 } 35 + err = ms.SignConcatMP4(context.Background(), bytes.NewReader(bs), inputFds, output) 36 + if err != nil { 37 + return err 38 } 39 + return nil 40 } 41 42 + func CombineSegmentsUnsigned(ctx context.Context, sources []io.ReadSeeker, w io.Writer, doH264Parse bool) error { 43 + ctx = log.WithLogValues(ctx, "mediafunc", "CombineSegmentsUnsigned") 44 ctx, cancel := context.WithCancel(ctx) 45 defer cancel() 46 47 pipelineSlice := []string{ 48 + fmt.Sprintf("mp4mux name=muxer faststart=true interleave-bytes=%d interleave-time=%d movie-timescale=60000 trak-timescale=60000 ! appsink sync=false name=mp4sink", InterleaveBytes, InterleaveTime), 49 + "capsfilter caps=video/x-h264,parsed=true name=videoqueue ! queue ! muxer.", 50 + "capsfilter caps=audio/x-opus,framed=true name=audioparse ! queue ! muxer.", 51 } 52 53 pipeline, err := gst.NewPipelineFromString(strings.Join(pipelineSlice, "\n")) ··· 58 segCh := make(chan *bus.Seg) 59 go func() { 60 for _, source := range sources { 61 + bs, err := io.ReadAll(source) 62 if err != nil { 63 err = fmt.Errorf("failed to read file: %w", err) 64 pipeline.Error(err.Error(), err) 65 return 66 } 67 + segCh <- &bus.Seg{ 68 + Filepath: "ignored", 69 + Data: bs, 70 + } 71 } 72 close(segCh) 73 }() 74 75 + concatBin, err := ConcatBin(ctx, segCh, doH264Parse) 76 if err != nil { 77 return fmt.Errorf("failed to create concat bin: %w", err) 78 } ··· 93 } 94 95 // Get the videoparse and audioparse elements from the pipeline 96 + videoQueue, err := pipeline.GetElementByName("videoqueue") 97 if err != nil { 98 return fmt.Errorf("failed to get video parse element: %w", err) 99 } ··· 104 } 105 106 // Link the concat bin pads to the parse element sink pads 107 + linked := videoPad.Link(videoQueue.GetStaticPad("sink")) 108 if linked != gst.PadLinkOK { 109 return fmt.Errorf("failed to link video pad to video parse element: %v", linked) 110 } ··· 120 return fmt.Errorf("failed to get mp4sink element: %w", err) 121 } 122 123 appSink := app.SinkFromElement(mp4Sink) 124 appSink.SetCallbacks(&app.SinkCallbacks{ 125 NewSampleFunc: WriterNewSample(ctx, w), 126 }) 127 128 // Start the pipeline ··· 139 140 // Handle bus messages 141 err = HandleBusMessages(ctx, pipeline) 142 + if err != nil { 143 + return fmt.Errorf("failed to handle bus messages: %w", err) 144 + } 145 146 if err != nil { 147 return fmt.Errorf("pipeline error: %w", err)
-34
pkg/media/clip_test.go
··· 1 - package media 2 - 3 - import ( 4 - "bytes" 5 - "context" 6 - "testing" 7 - 8 - "github.com/stretchr/testify/require" 9 - "golang.org/x/sync/errgroup" 10 - ) 11 - 12 - func TestClip(t *testing.T) { 13 - withNoGSTLeaks(t, func() { 14 - g, _ := errgroup.WithContext(context.Background()) 15 - for range streamplaceTestCount { 16 - g.Go(func() error { 17 - return innerTestClip(t) 18 - }) 19 - } 20 - err := g.Wait() 21 - require.NoError(t, err) 22 - }) 23 - } 24 - 25 - func innerTestClip(t *testing.T) error { 26 - fName := getFixture("sample-segment.mp4") 27 - inputFiles := []string{fName, fName, fName} 28 - buf := bytes.NewBuffer(nil) 29 - err := Clip(context.Background(), inputFiles, buf) 30 - require.NoError(t, err) 31 - require.Greater(t, buf.Len(), 2900000) 32 - require.Less(t, buf.Len(), 3100000) 33 - return nil 34 - }
···
+9 -3
pkg/media/clip_user.go
··· 4 "context" 5 "fmt" 6 "io" 7 "sort" 8 "time" 9 ··· 24 sort.Slice(segments, func(i, j int) bool { 25 return segments[i].StartTime.Before(segments[j].StartTime) 26 }) 27 - segmentFiles := []string{} 28 for _, segment := range segments { 29 aqt := aqtime.FromTime(segment.StartTime) 30 fpath, err := cli.SegmentFilePath(user, fmt.Sprintf("%s.%s", aqt.FileSafeString(), "mp4")) 31 if err != nil { 32 return fmt.Errorf("unable to get segment file path: %w", err) 33 } 34 - segmentFiles = append(segmentFiles, fpath) 35 } 36 - err = Clip(ctx, segmentFiles, writer) 37 if err != nil { 38 return fmt.Errorf("unable to clip segments: %w", err) 39 }
··· 4 "context" 5 "fmt" 6 "io" 7 + "os" 8 "sort" 9 "time" 10 ··· 25 sort.Slice(segments, func(i, j int) bool { 26 return segments[i].StartTime.Before(segments[j].StartTime) 27 }) 28 + segmentFiles := []io.ReadSeeker{} 29 for _, segment := range segments { 30 aqt := aqtime.FromTime(segment.StartTime) 31 fpath, err := cli.SegmentFilePath(user, fmt.Sprintf("%s.%s", aqt.FileSafeString(), "mp4")) 32 if err != nil { 33 return fmt.Errorf("unable to get segment file path: %w", err) 34 } 35 + fd, err := os.Open(fpath) 36 + if err != nil { 37 + return fmt.Errorf("unable to open segment file: %w", err) 38 + } 39 + defer fd.Close() 40 + segmentFiles = append(segmentFiles, fd) 41 } 42 + err = CombineSegmentsUnsigned(ctx, segmentFiles, writer, false) 43 if err != nil { 44 return fmt.Errorf("unable to clip segments: %w", err) 45 }
+21 -4
pkg/media/concat2.go
··· 12 13 var ErrConcatDone = errors.New("concat done") 14 15 - func ConcatBin(ctx context.Context, segCh <-chan *bus.Seg) (*gst.Bin, error) { 16 ctx = log.WithLogValues(ctx, "func", "ConcatBin") 17 bin := gst.NewBin("concat-bin") 18 ··· 59 return nil, fmt.Errorf("failed to add multiqueue to bin: %w", err) 60 } 61 62 mqVideoSink := mq.GetRequestPad("sink_%u") 63 if mqVideoSink == nil { 64 return nil, fmt.Errorf("video sink pad not found") ··· 114 select { 115 case seg := <-segCh: 116 if seg == nil { 117 ok := syncPadVideoSrc.PushEvent(gst.NewEOSEvent()) 118 if !ok { 119 log.Error(ctx, "failed to post EOS message", "error", ok) ··· 123 log.Error(ctx, "failed to post EOS message", "error", ok) 124 } 125 log.Debug(ctx, "concat completed") 126 return 127 } 128 - err := addConcatDemuxer(ctx, bin, seg, syncPadVideoSink, syncPadAudioSink) 129 if err != nil { 130 log.Error(ctx, "failed to add concat demuxer", "error", err) 131 bin.Error(err.Error(), err) ··· 140 return bin, nil 141 } 142 143 - func addConcatDemuxer(ctx context.Context, bin *gst.Bin, seg *bus.Seg, syncPadVideoSink *gst.Pad, syncPadAudioSink *gst.Pad) error { 144 var cancel context.CancelFunc 145 ctx, cancel = context.WithCancel(ctx) 146 defer cancel() 147 148 log.Debug(ctx, "adding concat demuxer", "seg", seg.Filepath) 149 - demuxBin, err := ConcatDemuxBin(ctx, seg) 150 if err != nil { 151 return fmt.Errorf("failed to create demux bin: %w", err) 152 }
··· 12 13 var ErrConcatDone = errors.New("concat done") 14 15 + func ConcatBin(ctx context.Context, segCh <-chan *bus.Seg, doH264Parse bool) (*gst.Bin, error) { 16 ctx = log.WithLogValues(ctx, "func", "ConcatBin") 17 bin := gst.NewBin("concat-bin") 18 ··· 59 return nil, fmt.Errorf("failed to add multiqueue to bin: %w", err) 60 } 61 62 + // 10x default multiqueue size 63 + err = mq.SetProperty("max-size-time", uint64(200000000000)) 64 + if err != nil { 65 + return nil, fmt.Errorf("failed to set max-size-time: %w", err) 66 + } 67 + err = mq.SetProperty("max-size-bytes", uint(1048576000)) 68 + if err != nil { 69 + return nil, fmt.Errorf("failed to set max-size-bytes: %w", err) 70 + } 71 + err = mq.SetProperty("max-size-buffers", uint(500)) 72 + if err != nil { 73 + return nil, fmt.Errorf("failed to set max-size-buffers: %w", err) 74 + } 75 + 76 mqVideoSink := mq.GetRequestPad("sink_%u") 77 if mqVideoSink == nil { 78 return nil, fmt.Errorf("video sink pad not found") ··· 128 select { 129 case seg := <-segCh: 130 if seg == nil { 131 + 132 ok := syncPadVideoSrc.PushEvent(gst.NewEOSEvent()) 133 if !ok { 134 log.Error(ctx, "failed to post EOS message", "error", ok) ··· 138 log.Error(ctx, "failed to post EOS message", "error", ok) 139 } 140 log.Debug(ctx, "concat completed") 141 + 142 return 143 } 144 + err := addConcatDemuxer(ctx, bin, seg, syncPadVideoSink, syncPadAudioSink, doH264Parse) 145 if err != nil { 146 log.Error(ctx, "failed to add concat demuxer", "error", err) 147 bin.Error(err.Error(), err) ··· 156 return bin, nil 157 } 158 159 + func addConcatDemuxer(ctx context.Context, bin *gst.Bin, seg *bus.Seg, syncPadVideoSink *gst.Pad, syncPadAudioSink *gst.Pad, doH264Parse bool) error { 160 var cancel context.CancelFunc 161 ctx, cancel = context.WithCancel(ctx) 162 defer cancel() 163 + ctx = log.WithLogValues(ctx, "func", "ConcatBin") 164 165 log.Debug(ctx, "adding concat demuxer", "seg", seg.Filepath) 166 + demuxBin, err := ConcatDemuxBin(ctx, seg, doH264Parse) 167 if err != nil { 168 return fmt.Errorf("failed to create demux bin: %w", err) 169 }
+2 -2
pkg/media/concat2_test.go
··· 95 close(segCh) 96 }() 97 98 - concatBin, err := ConcatBin(ctx, segCh) 99 if err != nil { 100 return fmt.Errorf("failed to create concat bin: %w", err) 101 } ··· 214 <-padIdleCh 215 <-padIdleCh 216 217 - require.Equal(t, 4936240, videoBuf.Len(), fmt.Sprintf("uuid: %s", uuidStr)) 218 require.Equal(t, 32200, audioBuf.Len(), fmt.Sprintf("uuid: %s", uuidStr)) 219 220 return <-errCh
··· 95 close(segCh) 96 }() 97 98 + concatBin, err := ConcatBin(ctx, segCh, true) 99 if err != nil { 100 return fmt.Errorf("failed to create concat bin: %w", err) 101 } ··· 214 <-padIdleCh 215 <-padIdleCh 216 217 + require.Equal(t, 4936455, videoBuf.Len(), fmt.Sprintf("uuid: %s", uuidStr)) 218 require.Equal(t, 32200, audioBuf.Len(), fmt.Sprintf("uuid: %s", uuidStr)) 219 220 return <-errCh
+79 -6
pkg/media/concat_demux.go
··· 18 // Function for demuxing a single segment. Needs to be handled very carefully. 19 // In particular: users of this MUST cancel the passed context when they're 20 // done with the bin. 21 - func ConcatDemuxBin(ctx context.Context, seg *bus.Seg) (*gst.Bin, error) { 22 - ctx = log.WithLogValues(ctx, "func", "SegDemuxBin") 23 bin := gst.NewBin("seg-demux-bin") 24 25 appSrc, err := gst.NewElementWithProperties("appsrc", map[string]interface{}{ ··· 56 57 mq, err := gst.NewElementWithProperties("multiqueue", map[string]interface{}{ 58 "name": "concat-demux-multiqueue", 59 }) 60 if err != nil { 61 return nil, fmt.Errorf("failed to create multiqueue element: %w", err) ··· 64 if err != nil { 65 return nil, fmt.Errorf("failed to add multiqueue to bin: %w", err) 66 } 67 68 mqVideoSink := mq.GetRequestPad("sink_%u") 69 if mqVideoSink == nil { ··· 85 return nil, fmt.Errorf("audio source pad not found") 86 } 87 88 - videoGhost := gst.NewGhostPad("video_0", mqVideoSrc) 89 - if videoGhost == nil { 90 - return nil, fmt.Errorf("failed to create video ghost pad") 91 } 92 93 - audioGhost := gst.NewGhostPad("audio_0", mqAudioSrc) 94 if audioGhost == nil { 95 return nil, fmt.Errorf("failed to create audio ghost pad") 96 }
··· 18 // Function for demuxing a single segment. Needs to be handled very carefully. 19 // In particular: users of this MUST cancel the passed context when they're 20 // done with the bin. 21 + func ConcatDemuxBin(ctx context.Context, seg *bus.Seg, doH264Parse bool) (*gst.Bin, error) { 22 + ctx = log.WithLogValues(ctx, "func", "ConcatDemuxBin") 23 bin := gst.NewBin("seg-demux-bin") 24 25 appSrc, err := gst.NewElementWithProperties("appsrc", map[string]interface{}{ ··· 56 57 mq, err := gst.NewElementWithProperties("multiqueue", map[string]interface{}{ 58 "name": "concat-demux-multiqueue", 59 + // "max-size-time": uint(0), // default: 2000000000, 2 seconds 60 + // "max-size-bytes": uint(0), // default: 10485760, 10MiB 61 + // "max-size-buffers": uint(0), // default: 5, 5 buffers 62 }) 63 if err != nil { 64 return nil, fmt.Errorf("failed to create multiqueue element: %w", err) ··· 67 if err != nil { 68 return nil, fmt.Errorf("failed to add multiqueue to bin: %w", err) 69 } 70 + // err = mq.SetProperty("max-size-time", uint64(200000000000)) 71 + // if err != nil { 72 + // return nil, fmt.Errorf("failed to set max-size-time: %w", err) 73 + // } 74 + // err = mq.SetProperty("max-size-bytes", uint(1048576000)) 75 + // if err != nil { 76 + // return nil, fmt.Errorf("failed to set max-size-bytes: %w", err) 77 + // } 78 + // err = mq.SetProperty("max-size-buffers", uint(500)) 79 + // if err != nil { 80 + // return nil, fmt.Errorf("failed to set max-size-buffers: %w", err) 81 + // } 82 + 83 + opusparse, err := gst.NewElementWithProperties("opusparse", map[string]interface{}{ 84 + "name": "concat-demux-opusparse", 85 + "disable-passthrough": true, 86 + }) 87 + if err != nil { 88 + return nil, fmt.Errorf("failed to create opusparse element: %w", err) 89 + } 90 + err = bin.Add(opusparse) 91 + if err != nil { 92 + return nil, fmt.Errorf("failed to add opusparse to bin: %w", err) 93 + } 94 + opusparseSinkPad := opusparse.GetStaticPad("sink") 95 + if opusparseSinkPad == nil { 96 + return nil, fmt.Errorf("failed to get opusparse sink pad") 97 + } 98 + opusparseSrcPad := opusparse.GetStaticPad("src") 99 + if opusparseSrcPad == nil { 100 + return nil, fmt.Errorf("failed to get opusparse source pad") 101 + } 102 103 mqVideoSink := mq.GetRequestPad("sink_%u") 104 if mqVideoSink == nil { ··· 120 return nil, fmt.Errorf("audio source pad not found") 121 } 122 123 + linked := mqAudioSrc.Link(opusparseSinkPad) 124 + if linked != gst.PadLinkOK { 125 + return nil, fmt.Errorf("failed to link opusparse sink pad to mq audio sink pad") 126 + } 127 + 128 + var videoGhost *gst.GhostPad 129 + if doH264Parse { 130 + h264parse, err := gst.NewElementWithProperties("h264parse", map[string]interface{}{ 131 + "name": "concat-demux-h264parse", 132 + "config-interval": -1, 133 + "disable-passthrough": true, 134 + }) 135 + if err != nil { 136 + return nil, fmt.Errorf("failed to create h264parse element: %w", err) 137 + } 138 + err = bin.Add(h264parse) 139 + if err != nil { 140 + return nil, fmt.Errorf("failed to add h264parse to bin: %w", err) 141 + } 142 + h264parseSinkPad := h264parse.GetStaticPad("sink") 143 + if h264parseSinkPad == nil { 144 + return nil, fmt.Errorf("failed to get h264parse sink pad") 145 + } 146 + h264parseSrcPad := h264parse.GetStaticPad("src") 147 + if h264parseSrcPad == nil { 148 + return nil, fmt.Errorf("failed to get h264parse source pad") 149 + } 150 + linked := mqVideoSrc.Link(h264parseSinkPad) 151 + if linked != gst.PadLinkOK { 152 + return nil, fmt.Errorf("failed to link h264parse sink pad to mq video sink pad") 153 + } 154 + 155 + videoGhost = gst.NewGhostPad("video_0", h264parseSrcPad) 156 + if videoGhost == nil { 157 + return nil, fmt.Errorf("failed to create video ghost pad") 158 + } 159 + } else { 160 + videoGhost = gst.NewGhostPad("video_0", mqVideoSrc) 161 + if videoGhost == nil { 162 + return nil, fmt.Errorf("failed to create video ghost pad") 163 + } 164 } 165 166 + audioGhost := gst.NewGhostPad("audio_0", opusparseSrcPad) 167 if audioGhost == nil { 168 return nil, fmt.Errorf("failed to create audio ghost pad") 169 }
+2 -3
pkg/media/concat_demux_test.go
··· 29 }) 30 } 31 32 - // This function remains in scope for the duration of a single users' playback 33 func innerTestConcatDemuxBin(t *testing.T) error { 34 ctx := log.WithDebugValue(context.Background(), map[string]map[string]int{"func": {"ConcatStream": 9, "TestConcat2": 9, "SegDemuxBin": 9}}) 35 ctx = log.WithLogValues(ctx, "func", "TestConcat2") ··· 65 Filepath: filename, 66 } 67 68 - concatBin, err := ConcatDemuxBin(ctx, testSeg) 69 if err != nil { 70 return fmt.Errorf("failed to create concat bin: %w", err) 71 } ··· 158 if err != nil { 159 t.Errorf("failed to set pipeline to null state: %v", err) 160 } 161 - require.Equal(t, 987248, videoBuf.Len()) 162 require.Equal(t, 6440, audioBuf.Len()) 163 }() 164
··· 29 }) 30 } 31 32 func innerTestConcatDemuxBin(t *testing.T) error { 33 ctx := log.WithDebugValue(context.Background(), map[string]map[string]int{"func": {"ConcatStream": 9, "TestConcat2": 9, "SegDemuxBin": 9}}) 34 ctx = log.WithLogValues(ctx, "func", "TestConcat2") ··· 64 Filepath: filename, 65 } 66 67 + concatBin, err := ConcatDemuxBin(ctx, testSeg, true) 68 if err != nil { 69 return fmt.Errorf("failed to create concat bin: %w", err) 70 } ··· 157 if err != nil { 158 t.Errorf("failed to set pipeline to null state: %v", err) 159 } 160 + require.Equal(t, 987291, videoBuf.Len()) 161 require.Equal(t, 6440, audioBuf.Len()) 162 }() 163
+4
pkg/media/constants.go
···
··· 1 + package media 2 + 3 + var InterleaveBytes = uint64(0) 4 + var InterleaveTime = uint64(0)
+2 -1
pkg/media/media.go
··· 197 Livestream *streamplace.Livestream 198 } 199 200 var ErrInvalidMetadata = errors.New("invalid segment metadata") 201 202 func ParseSegmentAssertions(ctx context.Context, mani *c2patypes.Manifest) (*SegmentMetadata, error) { ··· 210 } 211 } 212 if ass == nil { 213 - return nil, fmt.Errorf("couldn't find %s assertions", StreamplaceMetadata) 214 } 215 proc := ld.NewJsonLdProcessor() 216 options := ld.NewJsonLdOptions("")
··· 197 Livestream *streamplace.Livestream 198 } 199 200 + var ErrMissingMetadata = errors.New("missing segment metadata") 201 var ErrInvalidMetadata = errors.New("invalid segment metadata") 202 203 func ParseSegmentAssertions(ctx context.Context, mani *c2patypes.Manifest) (*SegmentMetadata, error) { ··· 211 } 212 } 213 if ass == nil { 214 + return nil, ErrMissingMetadata 215 } 216 proc := ld.NewJsonLdProcessor() 217 options := ld.NewJsonLdOptions("")
+80 -7
pkg/media/media_data_parser.go
··· 17 "stream.place/streamplace/pkg/model" 18 ) 19 20 func ParseSegmentMediaData(ctx context.Context, mp4bs []byte) (*model.SegmentMediaData, error) { 21 ctx, span := otel.Tracer("signer").Start(ctx, "ParseSegmentMediaData") 22 defer span.End() ··· 25 defer cancel() 26 pipelineSlice := []string{ 27 "appsrc name=appsrc ! qtdemux name=demux", 28 - "demux.video_0 ! queue ! h264parse name=videoparse disable-passthrough=true config-interval=-1 ! h2642json ! appsink sync=false name=jsonappsink", 29 - "demux.audio_0 ! queue ! opusparse name=audioparse ! fakesink sync=false", 30 } 31 32 pipeline, err := gst.NewPipelineFromString(strings.Join(pipelineSlice, "\n")) ··· 44 45 src := app.SrcFromElement(appsrc) 46 src.SetCallbacks(&app.SourceCallbacks{ 47 - NeedDataFunc: ReaderNeedData(ctx, bytes.NewReader(mp4bs)), 48 }) 49 50 onPadAdded := func(element *gst.Element, pad *gst.Pad) { 51 caps := pad.GetCurrentCaps() 52 if caps == nil { 53 log.Warn(ctx, "Unable to get pad caps") 54 cancel() 55 return 56 } 57 58 structure := caps.GetStructureAt(0) 59 if structure == nil { ··· 213 return nil, fmt.Errorf("error decoding JSON object: %w", err) 214 } 215 216 - if videoMetadata == nil { 217 - return nil, fmt.Errorf("no video metadata") 218 } 219 - if audioMetadata == nil { 220 - return nil, fmt.Errorf("no audio metadata") 221 } 222 223 videoMetadata.BFrames = hasBFrames ··· 236 237 return meta, nil 238 }
··· 17 "stream.place/streamplace/pkg/model" 18 ) 19 20 + func padProbeEmpty(_ *gst.Pad, _ *gst.PadProbeInfo) gst.PadProbeReturn { 21 + return gst.PadProbeOK 22 + } 23 + 24 func ParseSegmentMediaData(ctx context.Context, mp4bs []byte) (*model.SegmentMediaData, error) { 25 ctx, span := otel.Tracer("signer").Start(ctx, "ParseSegmentMediaData") 26 defer span.End() ··· 29 defer cancel() 30 pipelineSlice := []string{ 31 "appsrc name=appsrc ! qtdemux name=demux", 32 + "demux.video_0 ! queue ! tee name=videotee", 33 + "videotee. ! queue ! h2642json ! appsink sync=false name=jsonappsink", 34 + "videotee. ! queue ! appsink sync=false name=videoappsink", 35 + "demux.audio_0 ! queue ! opusparse name=audioparse ! appsink sync=false name=audioappsink", 36 } 37 38 pipeline, err := gst.NewPipelineFromString(strings.Join(pipelineSlice, "\n")) ··· 50 51 src := app.SrcFromElement(appsrc) 52 src.SetCallbacks(&app.SourceCallbacks{ 53 + NeedDataFunc: ReaderNeedDataIncremental(ctx, bytes.NewReader(mp4bs)), 54 + }) 55 + 56 + foundSomeAudio := false 57 + audioSinkElem, err := pipeline.GetElementByName("audioappsink") 58 + if err != nil { 59 + return nil, fmt.Errorf("error creating SegmentMetadata pipeline: %w", err) 60 + } 61 + audioSink := app.SinkFromElement(audioSinkElem) 62 + if audioSink == nil { 63 + return nil, fmt.Errorf("error creating SegmentMetadata pipeline: %w", err) 64 + } 65 + audioSink.SetCallbacks(&app.SinkCallbacks{ 66 + NewSampleFunc: ParseSegmentMediaDataSinkNewSampleFunc(ctx, &foundSomeAudio), 67 + }) 68 + 69 + foundSomeVideo := false 70 + videoSinkElem, err := pipeline.GetElementByName("videoappsink") 71 + if err != nil { 72 + return nil, fmt.Errorf("error creating SegmentMetadata pipeline: %w", err) 73 + } 74 + videoSink := app.SinkFromElement(videoSinkElem) 75 + if videoSink == nil { 76 + return nil, fmt.Errorf("error creating SegmentMetadata pipeline: %w", err) 77 + } 78 + videoSink.SetCallbacks(&app.SinkCallbacks{ 79 + NewSampleFunc: ParseSegmentMediaDataSinkNewSampleFunc(ctx, &foundSomeVideo), 80 }) 81 + padsAdded := 0 82 + 83 + var padProbe func(pad *gst.Pad, info *gst.PadProbeInfo) gst.PadProbeReturn 84 + padProbe = func(pad *gst.Pad, info *gst.PadProbeInfo) gst.PadProbeReturn { 85 + if info.GetEvent().Type() != gst.EventTypeEOS { 86 + return gst.PadProbeOK 87 + } 88 + if padsAdded != 2 { 89 + err := fmt.Errorf("expected 2 tracks in input, got %d", padsAdded) 90 + pipeline.Error(err.Error(), err) 91 + } 92 + padProbe = padProbeEmpty 93 + return gst.PadProbeRemove 94 + } 95 + 96 + outerPadProbe := func(pad *gst.Pad, info *gst.PadProbeInfo) gst.PadProbeReturn { 97 + return padProbe(pad, info) 98 + } 99 100 onPadAdded := func(element *gst.Element, pad *gst.Pad) { 101 + padsAdded += 1 102 caps := pad.GetCurrentCaps() 103 if caps == nil { 104 log.Warn(ctx, "Unable to get pad caps") 105 cancel() 106 return 107 } 108 + 109 + pad.AddProbe(gst.PadProbeTypeEventBoth, outerPadProbe) 110 111 structure := caps.GetStructureAt(0) 112 if structure == nil { ··· 266 return nil, fmt.Errorf("error decoding JSON object: %w", err) 267 } 268 269 + if videoMetadata == nil || !foundSomeVideo { 270 + return nil, fmt.Errorf("no video in segment") 271 } 272 + if audioMetadata == nil || !foundSomeAudio { 273 + return nil, fmt.Errorf("no audio in segment") 274 } 275 276 videoMetadata.BFrames = hasBFrames ··· 289 290 return meta, nil 291 } 292 + 293 + func ParseSegmentMediaDataSinkNewSampleFunc(ctx context.Context, foundThisTrack *bool) func(sink *app.Sink) gst.FlowReturn { 294 + return func(sink *app.Sink) gst.FlowReturn { 295 + sample := sink.PullSample() 296 + if sample == nil { 297 + return gst.FlowOK 298 + } 299 + buf := sample.GetBuffer() 300 + if buf == nil { 301 + return gst.FlowError 302 + } 303 + dur := buf.Duration().AsDuration() 304 + if dur != nil && *dur > 0 { 305 + *foundThisTrack = true 306 + } else { 307 + log.Warn(ctx, "no duration found for track", "track", sink.GetName()) 308 + } 309 + return gst.FlowOK 310 + } 311 + }
+16
pkg/media/media_data_parser_test.go
··· 15 segmentsWithoutBFrames := []string{ 16 remote.RemoteFixture("d63d26050db9a60c0944b4c2e2b1d052c4350a2a8a877324c7b0b7e7a0c1ae27/bframe-false-positive.mp4"), 17 getFixture("sample-segment.mp4"), 18 } 19 withNoGSTLeaks(t, func() { 20 for _, segment := range segmentsWithoutBFrames { ··· 51 require.Greater(t, mediaData.Duration, int64(0), "Video duration should not be empty") 52 }) 53 }
··· 15 segmentsWithoutBFrames := []string{ 16 remote.RemoteFixture("d63d26050db9a60c0944b4c2e2b1d052c4350a2a8a877324c7b0b7e7a0c1ae27/bframe-false-positive.mp4"), 17 getFixture("sample-segment.mp4"), 18 + remote.RemoteFixture("604bebf51c97f27aa07a8952462ac9885dd963f7a88375154217f59db32e1573/2025-11-18T01-10-56-292Z-signed-segment.mp4"), 19 } 20 withNoGSTLeaks(t, func() { 21 for _, segment := range segmentsWithoutBFrames { ··· 52 require.Greater(t, mediaData.Duration, int64(0), "Video duration should not be empty") 53 }) 54 } 55 + 56 + func TestMediaDataParserVideoHeaderWithNoVideo(t *testing.T) { 57 + withNoGSTLeaks(t, func() { 58 + inputFile, err := os.Open(remote.RemoteFixture("0aa38ed08bb6b6b0ae5f4891a97244717e2c952d5ca878e34450729770f7ca53/2025-11-16T23-05-04-512Z-converge-segment-did-key-zQ3shkzEYN8UrJoRAGS6pgPodXjdg8kF2fXQNGfJhpg3x4KJT.mp4")) 59 + require.NoError(t, err) 60 + defer inputFile.Close() 61 + bs, err := io.ReadAll(inputFile) 62 + require.NoError(t, err) 63 + 64 + ctx := log.WithDebugValue(context.Background(), map[string]map[string]int{"GStreamerFunc": {"ParseSegmentMediaData": 9}}) 65 + mediaData, err := ParseSegmentMediaData(ctx, bs) 66 + require.ErrorContains(t, err, "no video in segment") 67 + require.Nil(t, mediaData) 68 + }) 69 + }
+81 -1
pkg/media/media_signer.go
··· 6 "crypto/ecdsa" 7 "crypto/rand" 8 "crypto/sha256" 9 "encoding/json" 10 "fmt" 11 "io" 12 "time" 13 14 "go.opentelemetry.io/otel" 15 "stream.place/streamplace/pkg/aqtime" 16 "stream.place/streamplace/pkg/atproto" 17 c2patypes "stream.place/streamplace/pkg/c2patypes" ··· 29 Pub() aqpub.Pub 30 Streamer() string 31 DID() string 32 } 33 34 type MediaSignerLocal struct { 35 StreamerName string 36 Signer crypto.Signer ··· 40 did string 41 manifestBuilder *ManifestBuilder 42 PrebuiltManifest []byte // Optional: use this manifest instead of building one 43 } 44 45 func prepareCert(ctx context.Context, cli *config.CLI, signer crypto.Signer) ([]byte, error) { ··· 155 rustCallbackSigner := &RustCallbackSigner{ 156 Signer: ms.Signer, 157 } 158 - bs, err = iroh_streamplace.Sign(string(manifestBs), bs, ms.Cert, rustCallbackSigner) 159 if err != nil { 160 return nil, err 161 } ··· 170 spmetrics.SigningDuration.WithLabelValues(ms.StreamerName).Observe(float64(time.Since(startTime).Milliseconds())) 171 return bs, nil 172 } 173 174 func (ms *MediaSignerLocal) Pub() aqpub.Pub { 175 return ms.AQPub
··· 6 "crypto/ecdsa" 7 "crypto/rand" 8 "crypto/sha256" 9 + "encoding/base64" 10 "encoding/json" 11 "fmt" 12 "io" 13 "time" 14 15 "go.opentelemetry.io/otel" 16 + "stream.place/streamplace/pkg/aqio" 17 "stream.place/streamplace/pkg/aqtime" 18 "stream.place/streamplace/pkg/atproto" 19 c2patypes "stream.place/streamplace/pkg/c2patypes" ··· 31 Pub() aqpub.Pub 32 Streamer() string 33 DID() string 34 + SignConcatMP4(ctx context.Context, input io.ReadSeeker, ingredients []io.ReadSeeker, output io.ReadWriteSeeker) error 35 } 36 37 + var DoReplay = false 38 + 39 type MediaSignerLocal struct { 40 StreamerName string 41 Signer crypto.Signer ··· 45 did string 46 manifestBuilder *ManifestBuilder 47 PrebuiltManifest []byte // Optional: use this manifest instead of building one 48 + sigs [][]byte 49 } 50 51 func prepareCert(ctx context.Context, cli *config.CLI, signer crypto.Signer) ([]byte, error) { ··· 161 rustCallbackSigner := &RustCallbackSigner{ 162 Signer: ms.Signer, 163 } 164 + bs, err = iroh_streamplace.Sign(string(manifestBs), c2patypes.NewReader(aqio.NewReadWriteSeeker(bs)), base64.StdEncoding.EncodeToString(ms.Cert), rustCallbackSigner) 165 if err != nil { 166 return nil, err 167 } ··· 176 spmetrics.SigningDuration.WithLabelValues(ms.StreamerName).Observe(float64(time.Since(startTime).Milliseconds())) 177 return bs, nil 178 } 179 + 180 + func (ms *MediaSignerLocal) SignConcatMP4(ctx context.Context, input io.ReadSeeker, ingredients []io.ReadSeeker, output io.ReadWriteSeeker) error { 181 + startTime := time.Now() 182 + ctx, span := otel.Tracer("signer").Start(ctx, "SignMP4") 183 + defer span.End() 184 + // for _, ingredient := range ingredients { 185 + // _, err := iroh_streamplace.GetManifestAndCert(c2patypes.NewReader(aqio.NewReadWriteSeeker(ingredient))) 186 + // if err != nil { 187 + // return nil, err 188 + // } 189 + // } 190 + // title := "livestream" 191 + mani := obj{ 192 + "title": "Livestream Clip", 193 + // "assertions": []obj{ 194 + // { 195 + // "label": "c2pa.actions", 196 + // "data": obj{ 197 + // "actions": []obj{ 198 + // {"action": "c2pa.created"}, 199 + // {"action": "c2pa.published"}, 200 + // }, 201 + // }, 202 + // }, 203 + // { 204 + // "label": StreamplaceMetadata, 205 + // "data": obj{ 206 + // "@context": obj{ 207 + // "dc": "http://purl.org/dc/elements/1.1/", 208 + // }, 209 + // "dc:creator": ms.StreamerName, 210 + // "dc:title": []string{title}, 211 + // "dc:date": []string{aqtime.FromMillis(start).String()}, 212 + // }, 213 + // }, 214 + // }, 215 + } 216 + ctx, span = otel.Tracer("signer").Start(ctx, "SignMP4_MarshalManifest") 217 + manifestBs, err := json.Marshal(mani) 218 + if err != nil { 219 + return fmt.Errorf("failed to marshal manifest: %w", err) 220 + } 221 + var manifest c2patypes.ManifestDefinition 222 + err = json.Unmarshal(manifestBs, &manifest) 223 + if err != nil { 224 + return fmt.Errorf("failed to unmarshal manifest: %w", err) 225 + } 226 + span.End() 227 + 228 + ctx, span = otel.Tracer("signer").Start(ctx, "SignMP4_Sign") 229 + rustCallbackSigner := &RustCallbackSigner{ 230 + Signer: ms.Signer, 231 + } 232 + many := c2patypes.NewManyStreams() 233 + for _, ingredient := range ingredients { 234 + many.AddStream(ingredient) 235 + } 236 + err = iroh_streamplace.SignWithIngredients(string(manifestBs), c2patypes.NewReader(input), base64.StdEncoding.EncodeToString(ms.Cert), many, rustCallbackSigner, c2patypes.NewWriter(output)) 237 + if err != nil { 238 + return err 239 + } 240 + span.End() 241 + 242 + ctx, span = otel.Tracer("signer").Start(ctx, "SignMP4_OutputBytes") 243 + defer ctx.Done() 244 + if err != nil { 245 + return fmt.Errorf("failed to get output bytes: %w", err) 246 + } 247 + span.End() 248 + spmetrics.SigningDuration.WithLabelValues(ms.StreamerName).Observe(float64(time.Since(startTime).Milliseconds())) 249 + return nil 250 + } 251 + 252 + // don't call externally! this is used as a callback for the rust library 253 254 func (ms *MediaSignerLocal) Pub() aqpub.Pub { 255 return ms.AQPub
-154
pkg/media/media_signer_ext.go
··· 1 - package media 2 - 3 - import ( 4 - "bytes" 5 - "context" 6 - "crypto" 7 - "crypto/ecdsa" 8 - "fmt" 9 - "io" 10 - "os" 11 - "os/exec" 12 - "time" 13 - 14 - "github.com/decred/dcrd/dcrec/secp256k1" 15 - "github.com/mr-tron/base58" 16 - "go.opentelemetry.io/otel" 17 - "stream.place/streamplace/pkg/atproto" 18 - "stream.place/streamplace/pkg/config" 19 - "stream.place/streamplace/pkg/crypto/aqpub" 20 - "stream.place/streamplace/pkg/log" 21 - "stream.place/streamplace/pkg/model" 22 - "stream.place/streamplace/pkg/spmetrics" 23 - ) 24 - 25 - type MediaSignerExt struct { 26 - cli *config.CLI 27 - signer crypto.Signer 28 - pub aqpub.Pub 29 - certPath string 30 - streamer string 31 - keyBs []byte 32 - taURL string 33 - did string 34 - manifestBuilder *ManifestBuilder 35 - } 36 - 37 - func MakeMediaSignerExt(ctx context.Context, cli *config.CLI, streamer string, keyBs []byte, model model.Model) (MediaSigner, error) { 38 - key, _ := secp256k1.PrivKeyFromBytes(keyBs) 39 - if key == nil { 40 - return nil, fmt.Errorf("invalid authorization key (not valid secp256k1)") 41 - } 42 - var signer crypto.Signer = key.ToECDSA() 43 - certBs, err := prepareCert(ctx, cli, signer) 44 - if err != nil { 45 - return nil, err 46 - } 47 - // Write certificate to a temporary file 48 - certFile, err := os.CreateTemp("", "cert-*.pem") 49 - if err != nil { 50 - return nil, fmt.Errorf("failed to create temp cert file: %w", err) 51 - } 52 - defer certFile.Close() 53 - 54 - if _, err := certFile.Write(certBs); err != nil { 55 - return nil, fmt.Errorf("failed to write cert to temp file: %w", err) 56 - } 57 - 58 - certPath := certFile.Name() 59 - pub, err := aqpub.FromPublicKey(signer.Public().(*ecdsa.PublicKey)) 60 - if err != nil { 61 - return nil, err 62 - } 63 - did, err := atproto.ParsePubKey(signer.Public().(*ecdsa.PublicKey)) 64 - if err != nil { 65 - return nil, err 66 - } 67 - return &MediaSignerExt{ 68 - signer: signer, 69 - certPath: certPath, 70 - streamer: streamer, 71 - pub: pub, 72 - keyBs: keyBs, 73 - taURL: cli.TAURL, 74 - did: did.DIDKey(), 75 - manifestBuilder: NewManifestBuilder(model), 76 - }, nil 77 - } 78 - 79 - func (ms *MediaSignerExt) SignMP4(ctx context.Context, input io.ReadSeeker, start int64) ([]byte, error) { 80 - startTime := time.Now() 81 - _, span := otel.Tracer("signer").Start(ctx, "SignMP4_Ext") 82 - defer span.End() 83 - 84 - // Build manifest with metadata from database 85 - manifestBs, err := ms.manifestBuilder.BuildManifest(ctx, ms.streamer, start) 86 - if err != nil { 87 - log.Error(ctx, "MediaSignerExt: failed to build manifest", "error", err) 88 - return nil, fmt.Errorf("failed to build manifest: %w", err) 89 - } 90 - 91 - // Get the path to the current executable 92 - execPath, err := os.Executable() 93 - if err != nil { 94 - return nil, fmt.Errorf("failed to get executable path: %w", err) 95 - } 96 - 97 - enc := base58.Encode(ms.keyBs) 98 - 99 - // Prepare command with manifest JSON 100 - cmd := exec.Command(execPath, "sign", 101 - "--key", enc, 102 - "--cert", ms.certPath, 103 - "--ta-url", ms.taURL, 104 - "--streamer", ms.streamer, 105 - "--start-time", fmt.Sprintf("%d", start), 106 - "--manifest", string(manifestBs)) 107 - 108 - // overwrite so that our subprocesses don't do their own leak checking 109 - cmd.Env = append(os.Environ(), "LD_PRELOAD=") 110 - 111 - // Set up pipes for stdin and stdout 112 - stdin, err := cmd.StdinPipe() 113 - if err != nil { 114 - return nil, fmt.Errorf("failed to create stdin pipe: %w", err) 115 - } 116 - 117 - stdout := &bytes.Buffer{} 118 - cmd.Stdout = stdout 119 - stderr := &bytes.Buffer{} 120 - cmd.Stderr = stderr 121 - 122 - // Start the command 123 - if err := cmd.Start(); err != nil { 124 - return nil, fmt.Errorf("failed to start command: %w", err) 125 - } 126 - 127 - // Copy input to stdin 128 - _, err = io.Copy(stdin, input) 129 - if err != nil { 130 - return nil, fmt.Errorf("failed to write to stdin: %w stderr=%s", err, stderr.String()) 131 - } 132 - stdin.Close() 133 - 134 - // Wait for the command to complete 135 - if err := cmd.Wait(); err != nil { 136 - log.Error(ctx, "MediaSignerExt: subprocess failed", "error", err, "stderr", stderr.String()) 137 - return nil, fmt.Errorf("command failed: %w, stderr: %s", err, stderr.String()) 138 - } 139 - 140 - spmetrics.SigningDuration.WithLabelValues(ms.streamer).Observe(float64(time.Since(startTime).Milliseconds())) 141 - return stdout.Bytes(), nil 142 - } 143 - 144 - func (ms *MediaSignerExt) Pub() aqpub.Pub { 145 - return ms.pub 146 - } 147 - 148 - func (ms *MediaSignerExt) Streamer() string { 149 - return ms.streamer 150 - } 151 - 152 - func (ms *MediaSignerExt) DID() string { 153 - return ms.did 154 - }
···
-84
pkg/media/media_test.go
··· 2 3 import ( 4 "context" 5 - "os" 6 "path/filepath" 7 "runtime" 8 "testing" ··· 45 // require.NoError(t, err) 46 return mm, nil 47 } 48 - 49 - // func mp4(t *testing.T) []byte { 50 - // f, err := os.Open(getFixture("video.mpegts")) 51 - // require.NoError(t, err) 52 - // defer f.Close() 53 - // buf := bytes.Buffer{} 54 - // w := bufio.NewWriter(&buf) 55 - // err = MuxToMP4(context.Background(), f, w) 56 - // require.NoError(t, err) 57 - // return buf.Bytes() 58 - // } 59 - 60 - // func TestMuxToMP4(t *testing.T) { 61 - // bs := mp4(t) 62 - // require.Greater(t, len(bs), 0) 63 - // } 64 - 65 - // func TestSignMP4(t *testing.T) { 66 - // mp4bs := mp4(t) 67 - // r := bytes.NewReader(mp4bs) 68 - // _, ms := getStaticTestMediaManager(t) 69 - // millis := time.Now().UnixMilli() 70 - // bs, err := ms.SignMP4(context.Background(), r, millis) 71 - // require.NoError(t, err) 72 - // require.Greater(t, len(bs), 0) 73 - // } 74 - 75 - // func TestSignMP4WithWallet(t *testing.T) { 76 - // eip712test.WithTestSigner(func(signer *eip712.EIP712Signer) { 77 - // cli := ct.CLI(t, &config.CLI{ 78 - // TAURL: "http://timestamp.digicert.com", 79 - // AllowedStreams: []aqpub.Pub{}, 80 - // }) 81 - // ms, err := MakeMediaSigner(context.Background(), cli, "test person", signer) 82 - // require.NoError(t, err) 83 - // mp4bs := mp4(t) 84 - // r := bytes.NewReader(mp4bs) 85 - // millis := time.Now().UnixMilli() 86 - // bs, err := ms.SignMP4(context.Background(), r, millis) 87 - // require.NoError(t, err) 88 - // require.Greater(t, len(bs), 0) 89 - // }) 90 - // } 91 - 92 - // TODO: Would be good to have this tested with SoftHSM 93 - // func TestSignMP4WithHSM(t *testing.T) { 94 - // one := 1 95 - // sc, err := crypto11.Configure(&crypto11.Config{ 96 - // // TokenLabel: "C2PA Signer", 97 - // Path: "/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so", 98 - // Pin: "123456", 99 - // SlotNumber: &one, 100 - // }) 101 - // require.NoError(t, err) 102 - 103 - // allsigners, err := sc.FindAllKeyPairs() 104 - // require.NoError(t, err) 105 - // signer := allsigners[0] 106 - // certBs, err := signers.GenerateES256KCert(signer) 107 - // mm := MediaManager{ 108 - // cli: &config.CLI{ 109 - // TAURL: "http://timestamp.digicert.com", 110 - // }, 111 - // signer: signer, 112 - // cert: certBs, 113 - // user: "testuser", 114 - // } 115 - // mp4bs := mp4(t) 116 - // r := bytes.NewReader(mp4bs) 117 - // f, err := os.CreateTemp("", "*.mp4") 118 - // require.NoError(t, err) 119 - // ms := time.Now().UnixMilli() 120 - // err = mm.SignMP4(context.Background(), r, f, ms) 121 - // require.NoError(t, err) 122 - // } 123 - 124 - func TestVerifyMP4(t *testing.T) { 125 - f, err := os.Open(getFixture("sample-segment.mp4")) 126 - require.NoError(t, err) 127 - mm, _ := getStaticTestMediaManager(t) 128 - err = mm.ValidateMP4(context.Background(), f, true) 129 - require.NoError(t, err) 130 - }
··· 2 3 import ( 4 "context" 5 "path/filepath" 6 "runtime" 7 "testing" ··· 44 // require.NoError(t, err) 45 return mm, nil 46 }
+1 -1
pkg/media/packetize.go
··· 28 return nil, fmt.Errorf("failed to create GStreamer pipeline: %w", err) //nolint:all 29 } 30 31 - demuxBin, err := ConcatDemuxBin(ctx, seg) 32 if err != nil { 33 return nil, fmt.Errorf("failed to create concat bin: %w", err) 34 }
··· 28 return nil, fmt.Errorf("failed to create GStreamer pipeline: %w", err) //nolint:all 29 } 30 31 + demuxBin, err := ConcatDemuxBin(ctx, seg, true) 32 if err != nil { 33 return nil, fmt.Errorf("failed to create concat bin: %w", err) 34 }
+96 -36
pkg/media/rtcrec_test.go
··· 2 3 import ( 4 "context" 5 - "fmt" 6 "os" 7 "testing" 8 9 "github.com/pion/webrtc/v4" 10 "github.com/stretchr/testify/require" 11 "stream.place/streamplace/pkg/config" 12 "stream.place/streamplace/pkg/crypto/spkey" 13 - "stream.place/streamplace/pkg/globalerror" 14 "stream.place/streamplace/pkg/rtcrec" 15 ) 16 17 func TestRTCRecording(t *testing.T) { 18 - withNoGSTLeaks(t, func() { 19 - globalerror.GlobalErrors = []error{} 20 - ctx := context.Background() 21 - dir, err := os.MkdirTemp("", "rtcrec-test-*") 22 - require.NoError(t, err) 23 - defer os.RemoveAll(dir) 24 - cli := &config.CLI{} 25 - fs := cli.NewFlagSet("rtcrec-test") 26 - err = cli.Parse(fs, []string{"--data-dir", dir, "-wide-open=true"}) 27 - require.NoError(t, err) 28 - mm, err := MakeMediaManager(context.Background(), cli, nil, nil, nil, nil) 29 - require.NoError(t, err) 30 - priv, pub, err := spkey.GenerateStreamKey() 31 - require.NoError(t, err) 32 - signer, err := spkey.KeyToSigner(priv) 33 - require.NoError(t, err) 34 - mediaSigner, err := MakeMediaSigner(ctx, cli, pub.DIDKey(), signer, nil) 35 - require.NoError(t, err) 36 - // ctx := context.Background() 37 - // mm, ms := getStaticTestMediaManager(t) 38 - fd, err := os.Open(getFixture("intermittent-tracks.cbor")) 39 - require.NoError(t, err) 40 - defer fd.Close() 41 - pc, err := rtcrec.NewReplayPeerConnection(ctx, fd) 42 - require.NoError(t, err) 43 - done := make(chan struct{}) 44 - answer, err := mm.WebRTCIngest(ctx, &webrtc.SessionDescription{SDP: "placeholder"}, mediaSigner, pc, done) 45 - require.NoError(t, err) 46 - fmt.Println(answer.SDP) 47 - <-done 48 - for _, err := range globalerror.GlobalErrors { 49 - fmt.Printf("got error, non-fatal for now: %v\n", err) 50 - } 51 - }) 52 }
··· 2 3 import ( 4 "context" 5 "os" 6 "testing" 7 8 + "github.com/cenkalti/backoff/v5" 9 "github.com/pion/webrtc/v4" 10 "github.com/stretchr/testify/require" 11 + "go.uber.org/goleak" 12 "stream.place/streamplace/pkg/config" 13 "stream.place/streamplace/pkg/crypto/spkey" 14 "stream.place/streamplace/pkg/rtcrec" 15 + "stream.place/streamplace/test/remote" 16 ) 17 18 + var RTCRecTestCases = []struct { 19 + name string 20 + fatalErrors bool 21 + fixture string 22 + expectedSegmentsMin int 23 + expectedSegmentsMax int 24 + }{ 25 + { 26 + name: "IntermittentTracks", 27 + fatalErrors: false, 28 + fixture: getFixture("intermittent-tracks.cbor"), 29 + expectedSegmentsMin: 10, 30 + expectedSegmentsMax: 15, 31 + }, 32 + { 33 + name: "SegmentConvergenceIssues", 34 + fatalErrors: true, 35 + fixture: remote.RemoteFixture("6a1fb84e3c23405fc53161f59d5b837839c4889fc1a96533c82fb44fafc51d27/2025-11-14T22-41-20-399Z.cbor"), 36 + expectedSegmentsMin: 1, 37 + expectedSegmentsMax: 10, 38 + }, 39 + } 40 + 41 func TestRTCRecording(t *testing.T) { 42 + 43 + previous := FatalSegmentationErrors 44 + defer func() { 45 + FatalSegmentationErrors = previous 46 + }() 47 + // ctx := context.Background() 48 + // mm, ms := getStaticTestMediaManager(t) 49 + for _, testCase := range RTCRecTestCases { 50 + t.Run(testCase.name, func(t *testing.T) { 51 + withNoGSTLeaks(t, func() { 52 + ctx := context.Background() 53 + dir, err := os.MkdirTemp("", "rtcrec-test-*") 54 + require.NoError(t, err) 55 + defer os.RemoveAll(dir) 56 + cli := &config.CLI{} 57 + fs := cli.NewFlagSet("rtcrec-test") 58 + err = cli.Parse(fs, []string{ 59 + "--data-dir", dir, 60 + "-wide-open=true", 61 + }) 62 + require.NoError(t, err) 63 + mm, err := MakeMediaManager(context.Background(), cli, nil, nil, nil, nil) 64 + require.NoError(t, err) 65 + priv, pub, err := spkey.GenerateStreamKey() 66 + require.NoError(t, err) 67 + signer, err := spkey.KeyToSigner(priv) 68 + require.NoError(t, err) 69 + mediaSigner, err := MakeMediaSigner(ctx, cli, pub.DIDKey(), signer, nil) 70 + require.NoError(t, err) 71 + 72 + segsub := mm.NewSegment() 73 + segCount := 0 74 + go func() { 75 + for range segsub { 76 + segCount++ 77 + } 78 + }() 79 + 80 + cur := goleak.IgnoreCurrent() 81 + defer goleak.VerifyNone(t, cur) 82 + 83 + FatalSegmentationErrors = testCase.fatalErrors 84 + fd, err := os.Open(testCase.fixture) 85 + require.NoError(t, err) 86 + defer fd.Close() 87 + pc, err := rtcrec.NewReplayPeerConnection(ctx, fd) 88 + require.NoError(t, err) 89 + done := make(chan error) 90 + _, err = mm.WebRTCIngest(ctx, &webrtc.SessionDescription{SDP: "placeholder"}, mediaSigner, pc, done) 91 + require.NoError(t, err) 92 + // fmt.Println(answer.SDP) 93 + <-done 94 + 95 + // the segment getting ingested is ever so slightly after the done, which doesn't matter except in tests, just do a backoff for checking 96 + ticker := backoff.NewTicker(backoff.NewExponentialBackOff()) 97 + defer ticker.Stop() 98 + for i := 0; i < 10; i++ { 99 + if segCount >= testCase.expectedSegmentsMin { 100 + break 101 + } 102 + if i < 9 { 103 + <-ticker.C 104 + } 105 + } 106 + require.GreaterOrEqual(t, segCount, testCase.expectedSegmentsMin) 107 + require.LessOrEqual(t, segCount, testCase.expectedSegmentsMax) 108 + }) 109 + }) 110 + } 111 + 112 }
+53
pkg/media/segment_combine_test.go
···
··· 1 + package media 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "io" 7 + "os" 8 + "testing" 9 + 10 + "github.com/stretchr/testify/require" 11 + "golang.org/x/sync/errgroup" 12 + "stream.place/streamplace/pkg/aqio" 13 + "stream.place/streamplace/pkg/log" 14 + "stream.place/streamplace/test/remote" 15 + ) 16 + 17 + func TestCombineSegmentsUnsigned(t *testing.T) { 18 + withNoGSTLeaks(t, func() { 19 + g, _ := errgroup.WithContext(context.Background()) 20 + for range streamplaceTestCount { 21 + g.Go(func() error { 22 + return innerTestClip(t) 23 + }) 24 + } 25 + err := g.Wait() 26 + require.NoError(t, err) 27 + }) 28 + } 29 + 30 + func innerTestClip(t *testing.T) error { 31 + ctx := log.WithDebugValue(context.Background(), map[string]map[string]int{"func": {"ConcatDemuxBin": 9, "ConcatBin": 9}}) 32 + dirname := remote.RemoteArchive("c21e9352e72ca0729c66af2fcabec1b8997b509601241e8d38d5728f9687386b/threesegs.tar.gz") 33 + inputFiles := []string{ 34 + fmt.Sprintf("%s/2025-11-15T21-05-00-399Z.mp4", dirname), 35 + fmt.Sprintf("%s/2025-11-15T21-05-01-385Z.mp4", dirname), 36 + fmt.Sprintf("%s/2025-11-15T21-05-02-393Z.mp4", dirname), 37 + } 38 + inputFds := make([]io.ReadSeeker, len(inputFiles)) 39 + for i, fName := range inputFiles { 40 + fd, err := os.Open(fName) 41 + if err != nil { 42 + return fmt.Errorf("unable to open segment file: %w", err) 43 + } 44 + inputFds[i] = fd 45 + } 46 + buf := aqio.NewReadWriteSeeker([]byte{}) 47 + err := CombineSegmentsUnsigned(ctx, inputFds, buf, true) 48 + require.NoError(t, err) 49 + slice, err := buf.Bytes() 50 + require.NoError(t, err) 51 + require.Equal(t, 4725181, len(slice)) 52 + return nil 53 + }
+12 -24
pkg/media/segment_conv.go
··· 239 ctx, cancel := context.WithCancel(ctx) 240 defer cancel() 241 242 - // Handle bus messages in a separate goroutine 243 - g, ctx := errgroup.WithContext(ctx) 244 - g.Go(func() error { 245 - if err := HandleBusMessages(ctx, pipeline); err != nil { 246 - log.Log(ctx, "pipeline error", "error", err) 247 - } 248 - cancel() 249 - return nil 250 - }) 251 252 - // Start the pipeline 253 err = pipeline.SetState(gst.StatePlaying) 254 if err != nil { 255 return fmt.Errorf("failed to set pipeline state to playing: %w", err) 256 } 257 258 - // Wait for the pipeline to finish or context to be canceled 259 - <-ctx.Done() 260 - 261 - // durOk, dur := pipeline.QueryDuration(gst.FormatTime) 262 - // if !durOk { 263 - // return fmt.Errorf("failed to query duration") 264 - // } 265 - 266 - // Clean up 267 - err = pipeline.SetState(gst.StateNull) 268 - if err != nil { 269 - return fmt.Errorf("failed to set pipeline state to null: %w", err) 270 - } 271 272 - return nil 273 } 274 275 // Splits out video into MPEG-TS and audio into MP4 (to be recombined after transcoding)
··· 239 ctx, cancel := context.WithCancel(ctx) 240 defer cancel() 241 242 + busErr := make(chan error) 243 + go func() { 244 + err := HandleBusMessages(ctx, pipeline) 245 + busErr <- err 246 + }() 247 248 err = pipeline.SetState(gst.StatePlaying) 249 if err != nil { 250 return fmt.Errorf("failed to set pipeline state to playing: %w", err) 251 } 252 253 + defer func() { 254 + err = pipeline.SetState(gst.StateNull) 255 + if err != nil { 256 + log.Error(ctx, "failed to set pipeline state to null", "error", err) 257 + } 258 + }() 259 260 + return <-busErr 261 } 262 263 // Splits out video into MPEG-TS and audio into MP4 (to be recombined after transcoding)
+90
pkg/media/segment_converge.go
···
··· 1 + package media 2 + 3 + import ( 4 + "bytes" 5 + "context" 6 + "fmt" 7 + "io" 8 + "os" 9 + "path/filepath" 10 + "slices" 11 + 12 + "github.com/Eyevinn/mp4ff/mp4" 13 + "stream.place/streamplace/pkg/aqtime" 14 + "stream.place/streamplace/pkg/config" 15 + "stream.place/streamplace/pkg/log" 16 + ) 17 + 18 + var MaxSegmentTries = 10 19 + 20 + // run this segment through the segmenter/splitter until it comes out the 21 + // same, meaning we can cleanly get it in and out of a concatenated mp4 file 22 + func ConvergeSegment(ctx context.Context, cli *config.CLI, bs []byte, now int64, streamer string, doH264Parse bool) ([]byte, error) { 23 + cli.DumpDebugSegment(ctx, fmt.Sprintf("converge-segment-%s.mp4", streamer), bytes.NewReader(bs)) 24 + 25 + log.Debug(ctx, "parsing segment media data", "size", len(bs)) 26 + _, err := ParseSegmentMediaData(ctx, bs) 27 + if err != nil { 28 + return nil, fmt.Errorf("error parsing segment media data: %w", err) 29 + } 30 + // rewrite segmented audio timestamps to work around bug where the last 31 + // audio segment gets no duration and then gets dropped upon rewrite 32 + smearedBuf := &bytes.Buffer{} 33 + log.Debug(ctx, "rewriting audio timestamps", "size", len(bs)) 34 + err = RewriteAudioTimestamps(ctx, cli, bytes.NewReader(bs), smearedBuf, false) 35 + if err != nil { 36 + return nil, fmt.Errorf("error rewriting audio timestamps: %w", err) 37 + } 38 + bs = smearedBuf.Bytes() 39 + log.Debug(ctx, "converging segment", "size", len(bs)) 40 + 41 + previousBs := []byte{} 42 + currentBs := bs 43 + i := 0 44 + for i = 0; i <= MaxSegmentTries; i++ { 45 + if slices.Compare(previousBs, currentBs) == 0 { 46 + break 47 + } 48 + if cli.SegmentDebugDir != "" { 49 + mydir := filepath.Join(cli.SegmentDebugDir, streamer) 50 + err := os.MkdirAll(mydir, 0755) 51 + if err != nil { 52 + return nil, fmt.Errorf("failed to create debug directory: %w", err) 53 + } 54 + aqt := aqtime.FromMillis(now) 55 + outFile := filepath.Join(cli.SegmentDebugDir, fmt.Sprintf("%s-attempt-%03d.mp4", aqt.FileSafeString(), i)) 56 + err = os.WriteFile(outFile, currentBs, 0644) 57 + if err != nil { 58 + return nil, fmt.Errorf("failed to write debug file: %w", err) 59 + } 60 + log.Log(ctx, "wrote debug file", "path", outFile) 61 + } 62 + buf := bytes.Buffer{} 63 + err := CombineSegmentsUnsigned(ctx, []io.ReadSeeker{bytes.NewReader(currentBs)}, &buf, doH264Parse) 64 + if err != nil { 65 + return nil, fmt.Errorf("failed to attempt segment convergence: %w", err) 66 + } 67 + previousBs = currentBs 68 + currentBs = buf.Bytes() 69 + mp4file, err := mp4.DecodeFile(bytes.NewReader(currentBs)) 70 + if err != nil { 71 + return nil, fmt.Errorf("failed to decode segment: %w", err) 72 + } 73 + btrt := mp4file.Moov.Trak.Mdia.Minf.Stbl.Stsd.AvcX.Btrt 74 + btrt.AvgBitrate = 0 75 + btrt.MaxBitrate = 0 76 + // log.Log(ctx, "btrt", "average bitrate", btrt.AvgBitrate, "max bitrate", btrt.MaxBitrate) 77 + encodedBuf := bytes.Buffer{} 78 + err = mp4file.Encode(&encodedBuf) 79 + if err != nil { 80 + return nil, fmt.Errorf("failed to encode segment: %w", err) 81 + } 82 + currentBs = encodedBuf.Bytes() 83 + } 84 + if slices.Compare(previousBs, currentBs) != 0 { 85 + return nil, fmt.Errorf("failed to converge segment after %d tries", MaxSegmentTries) 86 + } 87 + bs = currentBs 88 + log.Debug(ctx, "converged segments", "tries", i, "size", len(bs)) 89 + return currentBs, nil 90 + }
+72
pkg/media/segment_converge_test.go
···
··· 1 + package media 2 + 3 + import ( 4 + "context" 5 + "os" 6 + "testing" 7 + 8 + "github.com/stretchr/testify/require" 9 + "stream.place/streamplace/pkg/config" 10 + "stream.place/streamplace/pkg/log" 11 + "stream.place/streamplace/test/remote" 12 + ) 13 + 14 + type ConvergeCase struct { 15 + Name string 16 + File string 17 + Success bool 18 + DoH264Parse bool 19 + } 20 + 21 + var ConvergeCases = []ConvergeCase{ 22 + { 23 + Name: "Good", 24 + File: remote.RemoteFixture("dbce5682132f9f1a8d92e1dcd66da99e4ae6eefd7429e4b168ed05d721a80379/2025-11-14T21-10-51-750Z-attempt-000.mp4"), 25 + Success: true, 26 + DoH264Parse: true, 27 + }, 28 + { 29 + Name: "Evil", 30 + File: remote.RemoteFixture("d81395168f8b2f3361d8e6d3443eeb678285a1973dc0b31e966cb81f5916db48/2025-11-14T21-10-57-754Z-attempt-000.mp4"), 31 + Success: true, 32 + DoH264Parse: true, 33 + }, 34 + { 35 + Name: "Stuck", 36 + File: remote.RemoteFixture("77e32825eaa9dfb8f6c7bbe3cb21213ffa01c1dc0d041f8e3e9cc4d107c95f16/2025-11-17T01-08-56-070Z-converge-segment-did-key-zQ3shX7nQpEqXEp3XFSPkS7mtUjQ3S1MNvxrEP2HeiwyPqmoz.mp4"), 37 + Success: true, 38 + DoH264Parse: false, 39 + }, 40 + { 41 + Name: "CouldNotMultiplex", 42 + File: remote.RemoteFixture("c12696bde28c5ab3ffd7040fd7da2ca6d871bb73b40fc2310dece0f6a082ee8a/2025-11-20T18-02-37-957Z-attempt-000.mp4"), 43 + Success: true, 44 + DoH264Parse: false, 45 + }, 46 + // { 47 + // Name: "CrashedPipeline", 48 + // File: "/Users/iameli/testvids/stuck-converge/2025-11-17T01-08-56-070Z-converge-segment-did-key-zQ3shX7nQpEqXEp3XFSPkS7mtUjQ3S1MNvxrEP2HeiwyPqmoz.mp4", 49 + // }, 50 + } 51 + 52 + func TestConvergeSegment(t *testing.T) { 53 + ctx := log.WithDebugValue(context.Background(), map[string]map[string]int{"func": {"ConcatDemuxBin": 9, "ConcatBin": 9}}) 54 + for _, tc := range ConvergeCases { 55 + tc := tc // capture for parallel tests if ever used 56 + t.Run(tc.Name, func(t *testing.T) { 57 + withNoGSTLeaks(t, func() { 58 + t.Log("--------------") 59 + t.Logf("test case: %s", tc.File) 60 + bs, err := os.ReadFile(tc.File) 61 + require.NoError(t, err) 62 + bs, err = ConvergeSegment(ctx, &config.CLI{}, bs, 0, "test-streamer", tc.DoH264Parse) 63 + if tc.Success { 64 + require.NoError(t, err) 65 + require.Greater(t, len(bs), 0) 66 + } else { 67 + require.Error(t, err) 68 + } 69 + }) 70 + }) 71 + } 72 + }
+181
pkg/media/segment_roundtrip_test.go
···
··· 1 + package media 2 + 3 + import ( 4 + "context" 5 + "crypto/sha256" 6 + "encoding/json" 7 + "fmt" 8 + "io" 9 + "os" 10 + "path/filepath" 11 + "reflect" 12 + "sort" 13 + "strings" 14 + "testing" 15 + 16 + "github.com/stretchr/testify/require" 17 + "stream.place/streamplace/pkg/aqio" 18 + "stream.place/streamplace/pkg/config" 19 + ct "stream.place/streamplace/pkg/config/configtesting" 20 + "stream.place/streamplace/pkg/crypto/spkey" 21 + "stream.place/streamplace/test/remote" 22 + ) 23 + 24 + var testTimestamp = "2025-01-01T00:00:00.000Z" 25 + 26 + func makeServerMediaSigner(t *testing.T) *MediaSignerLocal { 27 + priv, _, err := spkey.GenerateStreamKey() 28 + require.NoError(t, err) 29 + require.NoError(t, err) 30 + signer, err := spkey.KeyToSigner(priv) 31 + require.NoError(t, err) 32 + cli := ct.CLI(t, &config.CLI{ 33 + TAURL: "http://timestamp.digicert.com", 34 + WideOpen: true, 35 + }) 36 + msInterface, err := MakeMediaSigner(context.Background(), cli, "test-person", signer, nil) 37 + require.NoError(t, err) 38 + ms := msInterface.(*MediaSignerLocal) 39 + return ms 40 + } 41 + 42 + func TestSegmentRoundtrip(t *testing.T) { 43 + testCases := []struct { 44 + name string 45 + fixture string 46 + }{ 47 + { 48 + name: "OneMinute", 49 + fixture: remote.RemoteArchive("4563c7b48c0ca02c3fc87bbe6f1e63a743656e465a82bec0af75ef7eead04a23/1-minute-of-signed-segments.tar.gz"), 50 + }, 51 + { 52 + name: "ThreeSegs", 53 + fixture: remote.RemoteArchive("c21e9352e72ca0729c66af2fcabec1b8997b509601241e8d38d5728f9687386b/threesegs.tar.gz"), 54 + }, 55 + } 56 + for _, testCase := range testCases { 57 + t.Run(testCase.name, func(t *testing.T) { 58 + withNoGSTLeaks(t, func() { 59 + tempDir, err := os.MkdirTemp("", "ingredient_test") 60 + require.NoError(t, err) 61 + getTestVids := func() []io.ReadSeeker { 62 + testVids := []io.ReadSeeker{} 63 + segEntries, err := os.ReadDir(testCase.fixture) 64 + require.NoError(t, err) 65 + for _, segEntry := range segEntries { 66 + if segEntry.Type().IsRegular() { 67 + if !strings.HasSuffix(segEntry.Name(), ".mp4") { 68 + continue 69 + } 70 + fd, err := os.Open(filepath.Join(testCase.fixture, segEntry.Name())) 71 + require.NoError(t, err) 72 + testVids = append(testVids, fd) 73 + } 74 + } 75 + return testVids 76 + } 77 + 78 + firstReport, err := makeSegDirReport(t, testCase.fixture) 79 + require.NoError(t, err) 80 + ms := makeServerMediaSigner(t) 81 + rws := aqio.NewReadWriteSeeker([]byte{}) 82 + err = CombineSegments(context.Background(), getTestVids(), ms, rws) 83 + require.NoError(t, err) 84 + 85 + _, err = rws.Seek(0, io.SeekStart) 86 + require.NoError(t, err) 87 + 88 + signedSplitSegDir := makeTestSubdir(t, tempDir, "signed-split-segments") 89 + cli := &config.CLI{} 90 + fs := cli.NewFlagSet("rtcrec-test") 91 + err = cli.Parse(fs, []string{}) 92 + require.NoError(t, err) 93 + err = SplitSegments(context.Background(), cli, rws, func(fname string) ReadWriteSeekCloser { 94 + fd, err := os.Create(filepath.Join(signedSplitSegDir, fname)) 95 + require.NoError(t, err) 96 + return fd 97 + }) 98 + require.NoError(t, err) 99 + secondReport, err := makeSegDirReport(t, signedSplitSegDir) 100 + require.NoError(t, err) 101 + require.NoError(t, firstReport.CheckEquals(secondReport), "signed split segments are not equal to original segments") 102 + }) 103 + }) 104 + } 105 + } 106 + 107 + func makeTestSubdir(t *testing.T, tempDir, subdir string) string { 108 + subDir := filepath.Join(tempDir, subdir) 109 + err := os.MkdirAll(subDir, 0755) 110 + require.NoError(t, err) 111 + return subDir 112 + } 113 + 114 + type SegDirReport struct { 115 + Dir string 116 + Segs []string 117 + Hashes []string 118 + } 119 + 120 + func makeSegDirReport(t *testing.T, segDir string) (*SegDirReport, error) { 121 + segs := []string{} 122 + segEntries, err := os.ReadDir(segDir) 123 + require.NoError(t, err) 124 + for _, segEntry := range segEntries { 125 + if segEntry.Type().IsRegular() { 126 + segPath := filepath.Join(segDir, segEntry.Name()) 127 + segs = append(segs, segPath) 128 + } 129 + } 130 + sort.Strings(segs) 131 + hashes := make([]string, len(segs)) 132 + for i, segPath := range segs { 133 + hash, err := hashFile(segPath) 134 + if err != nil { 135 + return nil, err 136 + } 137 + hashes[i] = hash 138 + } 139 + 140 + return &SegDirReport{ 141 + Dir: segDir, 142 + Segs: segs, 143 + Hashes: hashes, 144 + }, nil 145 + } 146 + 147 + func (s *SegDirReport) Equals(other *SegDirReport) bool { 148 + if len(s.Segs) != len(other.Segs) { 149 + return false 150 + } 151 + if len(s.Hashes) != len(other.Hashes) { 152 + return false 153 + } 154 + return reflect.DeepEqual(s.Hashes, other.Hashes) 155 + } 156 + 157 + func hashFile(path string) (string, error) { 158 + bs, err := os.ReadFile(path) 159 + if err != nil { 160 + return "", err 161 + } 162 + hash := sha256.Sum256(bs) 163 + return fmt.Sprintf("%x", hash), nil 164 + } 165 + 166 + func (s *SegDirReport) CheckEquals(other *SegDirReport) error { 167 + if !s.Equals(other) { 168 + str1 := s.ToString() 169 + str2 := other.ToString() 170 + return fmt.Errorf("files should be equal: %s\n%s", str1, str2) 171 + } 172 + return nil 173 + } 174 + 175 + func (s *SegDirReport) ToString() string { 176 + bs, err := json.MarshalIndent(s, "", " ") 177 + if err != nil { 178 + panic(err) 179 + } 180 + return string(bs) 181 + }
+217
pkg/media/segment_split.go
···
··· 1 + package media 2 + 3 + import ( 4 + "bytes" 5 + "context" 6 + "encoding/json" 7 + "errors" 8 + "fmt" 9 + "io" 10 + "sort" 11 + 12 + "golang.org/x/sync/errgroup" 13 + "stream.place/streamplace/pkg/aqio" 14 + c2patypes "stream.place/streamplace/pkg/c2patypes" 15 + "stream.place/streamplace/pkg/config" 16 + "stream.place/streamplace/pkg/iroh/generated/iroh_streamplace" 17 + "stream.place/streamplace/pkg/log" 18 + ) 19 + 20 + type SplitSegment struct { 21 + Filename string 22 + Data []byte 23 + } 24 + 25 + type ManifestResult struct { 26 + Manifests map[string]c2patypes.Manifest `json:"manifests"` 27 + Certs map[string]string `json:"certs"` 28 + } 29 + 30 + type ManifestAndMetadata struct { 31 + Manifest c2patypes.Manifest 32 + SegmentMetadata *SegmentMetadata 33 + } 34 + 35 + type ReadWriteSeekCloser interface { 36 + io.ReadWriteSeeker 37 + io.Closer 38 + } 39 + 40 + type SegmentToSign struct { 41 + unsignedSeg io.ReadSeeker 42 + manifestId string 43 + cert []byte 44 + outputSeg io.ReadWriteSeeker 45 + closeCh chan struct{} 46 + } 47 + 48 + func NewSegmentToSign(unsignedSeg io.ReadSeeker, manifestId string, cert []byte, outputSeg io.ReadWriteSeeker) *SegmentToSign { 49 + return &SegmentToSign{ 50 + unsignedSeg: unsignedSeg, 51 + manifestId: manifestId, 52 + cert: cert, 53 + outputSeg: outputSeg, 54 + closeCh: make(chan struct{}), 55 + } 56 + } 57 + 58 + func (s *SegmentToSign) UnsignedSegStream() iroh_streamplace.Stream { 59 + return c2patypes.NewReader(s.unsignedSeg) 60 + } 61 + 62 + func (s *SegmentToSign) ManifestId() string { 63 + return s.manifestId 64 + } 65 + 66 + func (s *SegmentToSign) Cert() []byte { 67 + return s.cert 68 + } 69 + 70 + func (s *SegmentToSign) OutputSegStream() iroh_streamplace.Stream { 71 + return c2patypes.NewWriter(s.outputSeg) 72 + } 73 + 74 + func (s *SegmentToSign) Close() { 75 + close(s.closeCh) 76 + } 77 + 78 + func (s *SegmentToSign) Done() { 79 + <-s.closeCh 80 + } 81 + 82 + type ManySegmentsToSign struct { 83 + segmentCh chan iroh_streamplace.SegmentToSign 84 + readyCh chan struct{} 85 + } 86 + 87 + func (m *ManySegmentsToSign) Next() *iroh_streamplace.SegmentToSign { 88 + if m.readyCh != nil { 89 + close(m.readyCh) 90 + m.readyCh = nil 91 + } 92 + seg := <-m.segmentCh 93 + if seg == nil { 94 + return nil 95 + } 96 + return &seg 97 + } 98 + 99 + // split a signed concatenated mp4 into its constituent signed segments 100 + func SplitSegments(ctx context.Context, cli *config.CLI, input io.ReadSeeker, cb func(fname string) ReadWriteSeekCloser) error { 101 + manifestsStr, err := iroh_streamplace.GetManifests(c2patypes.NewReader(input)) 102 + if err != nil { 103 + return fmt.Errorf("failed to get manifests: %w", err) 104 + } 105 + _, err = input.Seek(0, io.SeekStart) 106 + if err != nil { 107 + return fmt.Errorf("failed to seek to start: %w", err) 108 + } 109 + var manifests ManifestResult 110 + err = json.Unmarshal([]byte(manifestsStr), &manifests) 111 + if err != nil { 112 + return fmt.Errorf("failed to unmarshal manifests: %w", err) 113 + } 114 + manifestList := []ManifestAndMetadata{} 115 + for _, manifest := range manifests.Manifests { 116 + metadata, err := ParseSegmentAssertions(context.Background(), &manifest) 117 + if errors.Is(err, ErrMissingMetadata) { 118 + log.Error(ctx, "missing metadata", "manifest", manifest.Label) 119 + continue 120 + } 121 + if err != nil { 122 + return fmt.Errorf("failed to parse segment assertions: %w", err) 123 + } 124 + manifestList = append(manifestList, ManifestAndMetadata{ 125 + Manifest: manifest, 126 + SegmentMetadata: metadata, 127 + }) 128 + } 129 + sort.Slice(manifestList, func(i, j int) bool { 130 + m1 := manifestList[i] 131 + m2 := manifestList[j] 132 + return m1.SegmentMetadata.StartTime.Time().Before(m2.SegmentMetadata.StartTime.Time()) 133 + }) 134 + certList := [][]byte{} 135 + for _, manifest := range manifestList { 136 + certList = append(certList, []byte(manifests.Certs[*manifest.Manifest.Label])) 137 + } 138 + 139 + segmentCh := make(chan iroh_streamplace.SegmentToSign) 140 + readyCh := make(chan struct{}) 141 + mss := &ManySegmentsToSign{ 142 + segmentCh: segmentCh, 143 + readyCh: readyCh, 144 + } 145 + g, ctx := errgroup.WithContext(ctx) 146 + unsignedCh := make(chan *SplitSegment) 147 + streamer := manifestList[0].SegmentMetadata.Creator 148 + 149 + // note: we're passing the input to two places here and need to make sure 150 + // they're not running into problems with concurrent seeking. so we use 151 + // this readyCh as a hack - it only fires after Rust is done with the input 152 + 153 + g.Go(func() error { 154 + err := iroh_streamplace.Resign(mss, c2patypes.NewReader(input)) 155 + if err != nil { 156 + return fmt.Errorf("failed to resign segments: %w", err) 157 + } 158 + return nil 159 + }) 160 + g.Go(func() error { 161 + defer close(unsignedCh) 162 + <-readyCh 163 + // rust is done with the input, rewind and start segmenting 164 + _, err := input.Seek(0, io.SeekStart) 165 + if err != nil { 166 + return fmt.Errorf("failed to seek to start: %w", err) 167 + } 168 + err = SegmentUnsigned(ctx, cli, streamer, input, true, unsignedCh) 169 + if err != nil { 170 + return fmt.Errorf("failed to segment file: %w", err) 171 + } 172 + return nil 173 + }) 174 + validationErrors := []error{} 175 + g.Go(func() error { 176 + defer close(segmentCh) 177 + i := 0 178 + for unsignedSeg := range unsignedCh { 179 + meta := manifestList[i].SegmentMetadata 180 + fname := fmt.Sprintf("%s.mp4", meta.StartTime.FileSafeString()) 181 + rwsc := cb(fname) 182 + rws := aqio.NewReadWriteSeeker(unsignedSeg.Data) 183 + ss := NewSegmentToSign(c2patypes.NewReader(rws), *manifestList[i].Manifest.Label, certList[i], rwsc) 184 + i += 1 185 + segmentCh <- ss 186 + ss.Done() 187 + _, err := rwsc.Seek(0, io.SeekStart) 188 + if err != nil { 189 + return fmt.Errorf("failed to seek to start: %w", err) 190 + } 191 + bs, err := io.ReadAll(rwsc) 192 + if err != nil { 193 + return fmt.Errorf("failed to read segment file: %w", err) 194 + } 195 + _, validationError := ValidateMP4Media(ctx, bs) 196 + if validationError != nil { 197 + validationErrors = append(validationErrors, validationError) 198 + cli.DumpDebugSegment(ctx, fmt.Sprintf("%s-invalid.mp4", fname), bytes.NewReader(bs)) 199 + } 200 + log.Log(ctx, "validated segment file", "path", fname) 201 + err = rwsc.Close() 202 + if err != nil { 203 + return fmt.Errorf("failed to close segment file: %w", err) 204 + } 205 + } 206 + return nil 207 + }) 208 + 209 + err = g.Wait() 210 + if err != nil { 211 + return fmt.Errorf("failed to split segments: %w", err) 212 + } 213 + if len(validationErrors) > 0 { 214 + return fmt.Errorf("%d errors validating segments; first error: %w", len(validationErrors), validationErrors[0]) 215 + } 216 + return nil 217 + }
+195 -30
pkg/media/segmenter.go
··· 4 "bytes" 5 "context" 6 "fmt" 7 "time" 8 9 "github.com/go-gst/go-gst/gst" 10 "github.com/go-gst/go-gst/gst/app" 11 - "go.opentelemetry.io/otel" 12 - "go.opentelemetry.io/otel/attribute" 13 - "go.opentelemetry.io/otel/trace" 14 - "stream.place/streamplace/pkg/globalerror" 15 "stream.place/streamplace/pkg/log" 16 ) 17 18 // element that takes the input stream, muxes to mp4, and signs the result 19 - func (mm *MediaManager) SegmentAndSignElem(ctx context.Context, ms MediaSigner) (*gst.Element, error) { 20 // elem, err := gst.NewElement("splitmuxsink name=splitter async-finalize=true sink-factory=appsink muxer-factory=matroskamux max-size-bytes=1") 21 elem, err := gst.NewElementWithProperties("splitmuxsink", map[string]any{ 22 "name": "signer", ··· 55 } 56 }() 57 58 _, err = elem.Connect("sink-added", func(split, sinkEle *gst.Element) { 59 buf := &bytes.Buffer{} 60 appsink := app.SinkFromElement(sinkEle) 61 if appsink == nil { 62 panic("appsink should not be nil") ··· 65 appsink.SetCallbacks(&app.SinkCallbacks{ 66 NewSampleFunc: WriterNewSample(ctx, buf), 67 EOSFunc: func(sink *app.Sink) { 68 - ctx, span := otel.Tracer("signer").Start(ctx, "SegmentAndSignElem", trace.WithAttributes( 69 - attribute.String("streamer", ms.Streamer()), 70 - )) 71 - defer span.End() 72 - resetTimer <- struct{}{} 73 now := time.Now().UnixMilli() 74 bs := buf.Bytes() 75 - if mm.cli.SmearAudio { 76 - smearedBuf := &bytes.Buffer{} 77 - err := SmearAudioTimestamps(ctx, bytes.NewReader(buf.Bytes()), smearedBuf) 78 if err != nil { 79 - log.Error(ctx, "error smearing audio timestamps", "error", err) 80 - return 81 } 82 - bs = smearedBuf.Bytes() 83 - } 84 - bs, err := ms.SignMP4(ctx, bytes.NewReader(bs), now) 85 - if err != nil { 86 - log.Error(ctx, "error signing segment", "error", err) 87 - return 88 - } 89 - 90 - err = mm.ValidateMP4(ctx, bytes.NewReader(bs), true) 91 if err != nil { 92 - log.Error(ctx, "error validating segment", "error", err) 93 - globalerror.GlobalError(err) 94 - // We don't want to stop the pipeline here because we want to keep the stream 95 - // alive. Lots of weird invalid data coming in from WebRTC connections on 96 - // phones. Better we drop one weird segment than force the stream to restart 97 - return 98 } 99 }, 100 }) ··· 105 106 return elem, nil 107 }
··· 4 "bytes" 5 "context" 6 "fmt" 7 + "io" 8 + "os" 9 + "strings" 10 "time" 11 12 "github.com/go-gst/go-gst/gst" 13 "github.com/go-gst/go-gst/gst/app" 14 + "stream.place/streamplace/pkg/config" 15 "stream.place/streamplace/pkg/log" 16 ) 17 18 + // For testing. Normally, We don't want to stop the pipeline upon a 19 + // segmentation error because we want to keep the stream alive. Lots 20 + // of weird invalid data coming in from WebRTC connections on phones. 21 + // Better we drop one weird segment than force the stream to restart. 22 + // But for tests, we want (sometimes) to know if there's a problem. 23 + var FatalSegmentationErrors = false 24 + 25 // element that takes the input stream, muxes to mp4, and signs the result 26 + func SegmentElem(ctx context.Context, cli *config.CLI, streamer string, doH264Parse bool, cb func(ctx context.Context, buf []byte, now int64) error) (*gst.Element, error) { 27 // elem, err := gst.NewElement("splitmuxsink name=splitter async-finalize=true sink-factory=appsink muxer-factory=matroskamux max-size-bytes=1") 28 elem, err := gst.NewElementWithProperties("splitmuxsink", map[string]any{ 29 "name": "signer", ··· 62 } 63 }() 64 65 + // we didn't need faststart but i'm leaving this commented here in case 66 + // you want to change any other muxer properties in the future 67 + 68 + _, err = elem.Connect("muxer-added", func(split, muxEle *gst.Element) { 69 + err := muxEle.SetProperty("presentation-time", false) 70 + if err != nil { 71 + panic("error setting presentation-time to false: " + err.Error()) 72 + } 73 + err = muxEle.SetProperty("interleave-bytes", InterleaveBytes) 74 + if err != nil { 75 + panic("error setting interleave-bytes" + err.Error()) 76 + } 77 + err = muxEle.SetProperty("interleave-time", InterleaveTime) 78 + if err != nil { 79 + panic("error setting interleave-time" + err.Error()) 80 + } 81 + err = muxEle.SetProperty("faststart", true) 82 + if err != nil { 83 + panic("error setting faststart" + err.Error()) 84 + } 85 + err = muxEle.SetProperty("movie-timescale", uint(60000)) 86 + if err != nil { 87 + panic("error setting movie-timescale" + err.Error()) 88 + } 89 + err = muxEle.SetProperty("trak-timescale", uint(60000)) 90 + if err != nil { 91 + panic("error setting trak-timescale" + err.Error()) 92 + } 93 + }) 94 + if err != nil { 95 + return nil, fmt.Errorf("failed to connect muxer-added handler: %w", err) 96 + } 97 + 98 + // channel to make sure data is emitted in order 99 + var ch chan struct{} 100 + 101 _, err = elem.Connect("sink-added", func(split, sinkEle *gst.Element) { 102 + previousSegCh := ch 103 + mySegCh := make(chan struct{}, 1) 104 + ch = mySegCh 105 buf := &bytes.Buffer{} 106 + err := sinkEle.SetProperty("sync", false) 107 + if err != nil { 108 + panic("error setting sync to false: " + err.Error()) 109 + } 110 appsink := app.SinkFromElement(sinkEle) 111 if appsink == nil { 112 panic("appsink should not be nil") ··· 115 appsink.SetCallbacks(&app.SinkCallbacks{ 116 NewSampleFunc: WriterNewSample(ctx, buf), 117 EOSFunc: func(sink *app.Sink) { 118 + // ctx, span := otel.Tracer("signer").Start(ctx, "SegmentAndSignElem", trace.WithAttributes( 119 + // attribute.String("streamer", ms.Streamer()), 120 + // )) 121 + // defer span.End() 122 now := time.Now().UnixMilli() 123 + resetTimer <- struct{}{} 124 bs := buf.Bytes() 125 + 126 + if previousSegCh != nil { 127 + <-previousSegCh 128 + } 129 + err := func() error { 130 + bs, err := ConvergeSegment(ctx, cli, bs, now, streamer, doH264Parse) 131 if err != nil { 132 + return fmt.Errorf("error converging segment: %w", err) 133 } 134 + log.Debug(ctx, "signing segment", "size", len(bs)) 135 + err = cb(ctx, bs, now) 136 + if err != nil { 137 + return fmt.Errorf("error signing segment: %w", err) 138 + } 139 + return nil 140 + }() 141 + close(mySegCh) 142 if err != nil { 143 + log.Error(ctx, "error in segmenter", "error", err) 144 + if FatalSegmentationErrors { 145 + sink.ErrorMessage(gst.DomainCore, gst.CoreErrorFailed, "error in segmenter", err.Error()) 146 + return 147 + } 148 } 149 }, 150 }) ··· 155 156 return elem, nil 157 } 158 + 159 + func (mm *MediaManager) SegmentAndSignElem(ctx context.Context, ms MediaSigner) (*gst.Element, error) { 160 + return SegmentElem(ctx, mm.cli, ms.Streamer(), false, func(ctx context.Context, bs []byte, now int64) error { 161 + if mm.cli.SmearAudio { 162 + smearedBuf := &bytes.Buffer{} 163 + err := RewriteAudioTimestamps(ctx, mm.cli, bytes.NewReader(bs), smearedBuf, true) 164 + if err != nil { 165 + return fmt.Errorf("error smearing audio timestamps: %w", err) 166 + } 167 + bs = smearedBuf.Bytes() 168 + } 169 + signedBs, err := ms.SignMP4(ctx, bytes.NewReader(bs), now) 170 + if err != nil { 171 + return fmt.Errorf("error calling SignMP4: %w", err) 172 + } 173 + log.Debug(ctx, "signed segment", "size", len(signedBs)) 174 + err = mm.ValidateMP4(ctx, bytes.NewReader(signedBs), true) 175 + if err != nil { 176 + mm.cli.DumpDebugSegment(ctx, "just-signed-segment.mp4", bytes.NewReader(signedBs)) 177 + return fmt.Errorf("error validating just-signed segment: %w", err) 178 + } 179 + return nil 180 + }) 181 + } 182 + 183 + func SegmentFileUnsigned(ctx context.Context, cli *config.CLI, streamer string, input string, ch chan *SplitSegment) error { 184 + fd, err := os.OpenFile(input, os.O_RDONLY, 0644) 185 + log.Log(ctx, "reading file", "file", input) 186 + if err != nil { 187 + return fmt.Errorf("failed to read file: %w", err) 188 + } 189 + defer fd.Close() 190 + return SegmentUnsigned(ctx, cli, streamer, fd, false, ch) 191 + } 192 + 193 + func SegmentUnsigned(ctx context.Context, cli *config.CLI, streamer string, input io.Reader, doH264Parse bool, ch chan *SplitSegment) error { 194 + ctx, cancel := context.WithCancel(ctx) 195 + defer cancel() 196 + pipelineSlice := []string{ 197 + "appsrc name=appsrc ! qtdemux name=demux", 198 + "demux. ! queue ! h264parse name=videoparse disable-passthrough=true config-interval=0", 199 + "demux. ! queue ! opusparse name=audioparse", 200 + } 201 + pipeline, err := gst.NewPipelineFromString(strings.Join(pipelineSlice, "\n")) 202 + if err != nil { 203 + return fmt.Errorf("error creating MKVIngest pipeline: %w", err) 204 + } 205 + 206 + srcele, err := pipeline.GetElementByName("appsrc") 207 + if err != nil { 208 + return err 209 + } 210 + src := app.SrcFromElement(srcele) 211 + src.SetCallbacks(&app.SourceCallbacks{ 212 + NeedDataFunc: ReaderNeedDataIncremental(ctx, input), 213 + }) 214 + videoParseEle, err := pipeline.GetElementByName("videoparse") 215 + if err != nil { 216 + return err 217 + } 218 + 219 + segmenter, err := SegmentElem(ctx, cli, streamer, doH264Parse, func(ctx context.Context, buf []byte, now int64) error { 220 + ch <- &SplitSegment{ 221 + Filename: fmt.Sprintf("%d.mp4", now), 222 + Data: buf, 223 + } 224 + return nil 225 + }) 226 + if err != nil { 227 + return err 228 + } 229 + 230 + err = pipeline.Add(segmenter) 231 + if err != nil { 232 + return err 233 + } 234 + err = videoParseEle.Link(segmenter) 235 + if err != nil { 236 + return err 237 + } 238 + audioparse, err := pipeline.GetElementByName("audioparse") 239 + if err != nil { 240 + return err 241 + } 242 + err = audioparse.Link(segmenter) 243 + if err != nil { 244 + return err 245 + } 246 + 247 + busErr := make(chan error) 248 + go func() { 249 + err := HandleBusMessages(ctx, pipeline) 250 + cancel() 251 + busErr <- err 252 + }() 253 + 254 + err = pipeline.SetState(gst.StatePlaying) 255 + if err != nil { 256 + return err 257 + } 258 + 259 + defer func() { 260 + err := pipeline.SetState(gst.StateNull) 261 + if err != nil { 262 + log.Error(ctx, "error setting pipeline to null state", "error", err) 263 + } 264 + }() 265 + 266 + err = <-busErr 267 + if err != nil { 268 + return err 269 + } 270 + 271 + return nil 272 + }
+72 -28
pkg/media/validate.go
··· 10 "strings" 11 "time" 12 13 "go.opentelemetry.io/otel" 14 "stream.place/streamplace/pkg/aqtime" 15 c2patypes "stream.place/streamplace/pkg/c2patypes" 16 "stream.place/streamplace/pkg/constants" ··· 21 ) 22 23 type ManifestAndCert struct { 24 - Manifest c2patypes.Manifest `json:"manifest"` 25 - Cert string `json:"cert"` 26 } 27 28 func (mm *MediaManager) ValidateMP4(ctx context.Context, input io.Reader, local bool) error { ··· 30 defer span.End() 31 buf, err := io.ReadAll(input) 32 if err != nil { 33 - return err 34 } 35 - var maniCert ManifestAndCert 36 - maniStr, err := iroh_streamplace.GetManifestAndCert(buf) 37 if err != nil { 38 - return err 39 - } 40 - err = json.Unmarshal([]byte(maniStr), &maniCert) 41 - if err != nil { 42 - return err 43 } 44 - label := maniCert.Manifest.Label 45 if label != nil && mm.model != nil { 46 oldSeg, err := mm.model.GetSegment(*label) 47 if err != nil { 48 - return err 49 } 50 if oldSeg != nil { 51 log.Warn(ctx, "segment already exists, skipping", "segmentID", *label) 52 return nil 53 } 54 } 55 - pub, err := signers.ParseES256KCert([]byte(maniCert.Cert)) 56 - if err != nil { 57 - return err 58 - } 59 - meta, err := ParseSegmentAssertions(ctx, &maniCert.Manifest) 60 - if err != nil { 61 - return err 62 - } 63 if meta.MetadataConfiguration != nil { 64 if meta.MetadataConfiguration.DistributionPolicy != nil { 65 allowedBroadcasters := meta.MetadataConfiguration.DistributionPolicy.AllowedBroadcasters ··· 70 } 71 } 72 } 73 - mediaData, err := ParseSegmentMediaData(ctx, buf) 74 - if err != nil { 75 - return err 76 - } 77 - // special case for test signers that are only signed with a key 78 var repoDID string 79 var signingKeyDID string 80 if strings.HasPrefix(meta.Creator, constants.DID_KEY_PREFIX) { 81 signingKeyDID = meta.Creator 82 repoDID = meta.Creator ··· 123 deleteAfter = meta.DistributionPolicy.ExpiresAt 124 } 125 seg := &model.Segment{ 126 - ID: *maniCert.Manifest.Label, 127 SigningKeyDID: signingKeyDID, 128 RepoDID: repoDID, 129 StartTime: meta.StartTime.Time(), ··· 150 case <-ctx.Done(): 151 return 152 case <-time.After(1 * time.Minute): 153 - log.Warn(ctx, "failed to send segment to channel, timing out", "streamer", repoDID, "signingKey", signingKeyDID, "segmentID", *maniCert.Manifest.Label) 154 return 155 } 156 }() 157 } 158 aqt := aqtime.FromTime(meta.StartTime.Time()) 159 - log.Log(ctx, "successfully ingested segment", "user", repoDID, "signingKey", signingKeyDID, "timestamp", aqt.FileSafeString(), "segmentID", *maniCert.Manifest.Label) 160 return nil 161 } 162 ··· 205 } 206 return false 207 }
··· 10 "strings" 11 "time" 12 13 + "github.com/bluesky-social/indigo/atproto/crypto" 14 "go.opentelemetry.io/otel" 15 + "stream.place/streamplace/pkg/aqio" 16 "stream.place/streamplace/pkg/aqtime" 17 c2patypes "stream.place/streamplace/pkg/c2patypes" 18 "stream.place/streamplace/pkg/constants" ··· 23 ) 24 25 type ManifestAndCert struct { 26 + Manifest c2patypes.Manifest `json:"manifest"` 27 + Cert string `json:"cert"` 28 + ValidationResults c2patypes.ValidationResults `json:"validation_results"` 29 } 30 31 func (mm *MediaManager) ValidateMP4(ctx context.Context, input io.Reader, local bool) error { ··· 33 defer span.End() 34 buf, err := io.ReadAll(input) 35 if err != nil { 36 + return fmt.Errorf("failed to read input: %w", err) 37 } 38 + 39 + valid, err := ValidateMP4Media(ctx, buf) 40 if err != nil { 41 + return fmt.Errorf("failed to validate MP4 media: %w", err) 42 } 43 + meta := valid.Meta 44 + pub := valid.Pub 45 + mediaData := valid.MediaData 46 + manifest := valid.Manifest 47 + 48 + label := manifest.Label 49 if label != nil && mm.model != nil { 50 oldSeg, err := mm.model.GetSegment(*label) 51 if err != nil { 52 + return fmt.Errorf("failed to get old segment: %w", err) 53 } 54 if oldSeg != nil { 55 log.Warn(ctx, "segment already exists, skipping", "segmentID", *label) 56 return nil 57 } 58 } 59 + 60 if meta.MetadataConfiguration != nil { 61 if meta.MetadataConfiguration.DistributionPolicy != nil { 62 allowedBroadcasters := meta.MetadataConfiguration.DistributionPolicy.AllowedBroadcasters ··· 67 } 68 } 69 } 70 + 71 var repoDID string 72 var signingKeyDID string 73 + // special case for test signers that are only signed with a key 74 if strings.HasPrefix(meta.Creator, constants.DID_KEY_PREFIX) { 75 signingKeyDID = meta.Creator 76 repoDID = meta.Creator ··· 117 deleteAfter = meta.DistributionPolicy.ExpiresAt 118 } 119 seg := &model.Segment{ 120 + ID: *label, 121 SigningKeyDID: signingKeyDID, 122 RepoDID: repoDID, 123 StartTime: meta.StartTime.Time(), ··· 144 case <-ctx.Done(): 145 return 146 case <-time.After(1 * time.Minute): 147 + log.Warn(ctx, "failed to send segment to channel, timing out", "streamer", repoDID, "signingKey", signingKeyDID, "segmentID", *label) 148 return 149 } 150 }() 151 } 152 aqt := aqtime.FromTime(meta.StartTime.Time()) 153 + log.Log(ctx, "successfully ingested segment", "user", repoDID, "signingKey", signingKeyDID, "timestamp", aqt.FileSafeString(), "segmentID", *label) 154 return nil 155 } 156 ··· 199 } 200 return false 201 } 202 + 203 + type ValidationResult struct { 204 + Pub *crypto.PublicKeyK256 205 + Meta *SegmentMetadata 206 + MediaData *model.SegmentMediaData 207 + Manifest *c2patypes.Manifest 208 + Cert string 209 + } 210 + 211 + // validate a signed mp4 file unto itself, ignoring whether this user is allowed and whatnot 212 + func ValidateMP4Media(ctx context.Context, buf []byte) (*ValidationResult, error) { 213 + var maniCert ManifestAndCert 214 + maniStr, err := iroh_streamplace.GetManifestAndCert(c2patypes.NewReader(aqio.NewReadWriteSeeker(buf))) 215 + if err != nil { 216 + return nil, err 217 + } 218 + err = json.Unmarshal([]byte(maniStr), &maniCert) 219 + if err != nil { 220 + return nil, fmt.Errorf("failed to unmarshal manifest and cert: %w", err) 221 + } 222 + activeManifest := maniCert.ValidationResults.ActiveManifest 223 + if activeManifest != nil { 224 + if activeManifest.Failure == nil { 225 + return nil, fmt.Errorf("active manifest failure array not found?!") 226 + } 227 + if len(activeManifest.Failure) > 0 { 228 + bs, _ := json.Marshal(activeManifest.Failure) 229 + return nil, fmt.Errorf("active manifest has failures: %s", string(bs)) 230 + } 231 + } 232 + pub, err := signers.ParseES256KCert([]byte(maniCert.Cert)) 233 + if err != nil { 234 + return nil, err 235 + } 236 + meta, err := ParseSegmentAssertions(ctx, &maniCert.Manifest) 237 + if err != nil { 238 + return nil, err 239 + } 240 + mediaData, err := ParseSegmentMediaData(ctx, buf) 241 + if err != nil { 242 + return nil, err 243 + } 244 + return &ValidationResult{ 245 + Pub: pub, 246 + Meta: meta, 247 + MediaData: mediaData, 248 + Manifest: &maniCert.Manifest, 249 + Cert: maniCert.Cert, 250 + }, nil 251 + }
+20 -17
pkg/media/webrtc_ingest.go
··· 16 ) 17 18 // This function remains in scope for the duration of a single users' playback 19 - func (mm *MediaManager) WebRTCIngest(ctx context.Context, offer *webrtc.SessionDescription, signer MediaSigner, peerConnection rtcrec.PeerConnection, done chan struct{}) (*webrtc.SessionDescription, error) { 20 uu, err := uuid.NewV7() 21 if err != nil { 22 return nil, err ··· 127 128 // Setup complete! Now we boot up streaming in the background while returning the SDP offer to the user. 129 go func() { 130 defer cancel() 131 - defer func() { close(done) }() 132 133 go func() { 134 ticker := time.NewTicker(time.Second * 1) ··· 143 } 144 }() 145 146 - go func() { 147 - if err := HandleBusMessages(ctx, pipeline); err != nil { 148 - log.Log(ctx, "pipeline error", "error", err) 149 - } 150 - cancel() 151 - }() 152 - 153 // subscription to bus messages for key revocation 154 go mm.HandleKeyRevocation(ctx, signer, pipeline) 155 ··· 222 i, _, readErr := track.Read(buf) 223 if readErr != nil { 224 log.Log(ctx, "failed to read track", "error", readErr) 225 - cancel() 226 - return 227 - } 228 - if ctx.Err() != nil { 229 return 230 } 231 if !videoFirst { 232 videoFirst = true 233 log.Debug(ctx, "got video data", "len", len(buf[:i])) ··· 261 i, _, readErr := track.Read(buf) 262 if readErr != nil { 263 log.Log(ctx, "failed to read track", "error", readErr) 264 - cancel() 265 - return 266 - } 267 - if ctx.Err() != nil { 268 return 269 } 270 if !audioFirst { 271 audioFirst = true 272 log.Debug(ctx, "got audio data", "len", len(buf[:i]))
··· 16 ) 17 18 // This function remains in scope for the duration of a single users' playback 19 + func (mm *MediaManager) WebRTCIngest(ctx context.Context, offer *webrtc.SessionDescription, signer MediaSigner, peerConnection rtcrec.PeerConnection, done chan error) (*webrtc.SessionDescription, error) { 20 uu, err := uuid.NewV7() 21 if err != nil { 22 return nil, err ··· 127 128 // Setup complete! Now we boot up streaming in the background while returning the SDP offer to the user. 129 go func() { 130 + busErrorChan := make(chan error) 131 + go func() { 132 + err := HandleBusMessages(ctx, pipeline) 133 + if err != nil { 134 + log.Log(ctx, "pipeline error", "error", err) 135 + } 136 + cancel() 137 + busErrorChan <- err 138 + }() 139 + 140 defer cancel() 141 + defer func() { done <- <-busErrorChan }() 142 143 go func() { 144 ticker := time.NewTicker(time.Second * 1) ··· 153 } 154 }() 155 156 // subscription to bus messages for key revocation 157 go mm.HandleKeyRevocation(ctx, signer, pipeline) 158 ··· 225 i, _, readErr := track.Read(buf) 226 if readErr != nil { 227 log.Log(ctx, "failed to read track", "error", readErr) 228 + videoSrc.EndStream() 229 return 230 } 231 + // if ctx.Err() != nil { 232 + // return 233 + // } 234 if !videoFirst { 235 videoFirst = true 236 log.Debug(ctx, "got video data", "len", len(buf[:i])) ··· 264 i, _, readErr := track.Read(buf) 265 if readErr != nil { 266 log.Log(ctx, "failed to read track", "error", readErr) 267 + audioSrc.EndStream() 268 return 269 } 270 + // if ctx.Err() != nil { 271 + // return 272 + // } 273 if !audioFirst { 274 audioFirst = true 275 log.Debug(ctx, "got audio data", "len", len(buf[:i]))
+1 -1
pkg/media/webrtc_playback.go
··· 73 } 74 }() 75 76 - concatBin, err := ConcatBin(ctx, segCh) 77 if err != nil { 78 return nil, fmt.Errorf("failed to create concat bin: %w", err) 79 }
··· 73 } 74 }() 75 76 + concatBin, err := ConcatBin(ctx, segCh, true) 77 if err != nil { 78 return nil, fmt.Errorf("failed to create concat bin: %w", err) 79 }
+10 -4
pkg/rtcrec/cmd/rtcrec.go
··· 22 } 23 24 func Start() error { 25 if len(os.Args) > 1 && os.Args[1] == "decode" { 26 return Decode() 27 } ··· 77 startCutoff = &time.Time{} 78 } 79 if endDuration == 0 { 80 - t := time.Unix(math.MaxInt64, 0) 81 endCutoff = &t 82 } 83 included := 0 ··· 103 } 104 continue 105 } 106 - if ev.Time.Before(*startCutoff) || ev.Time.After(*endCutoff) { 107 - // fmt.Printf("dropped: %s < %s\n", ev.Time.Format(time.RFC3339Nano), cutoff.Format(time.RFC3339Nano)) 108 dropped++ 109 continue 110 } ··· 119 func Decode() error { 120 var path string 121 flag.StringVar(&path, "path", "", "path to the file to decode") 122 - flag.Parse() 123 if path == "" { 124 return fmt.Errorf("path is required") 125 }
··· 22 } 23 24 func Start() error { 25 + if len(os.Args) == 1 { 26 + return fmt.Errorf("usage: rtcrec [decode|trim]") 27 + } 28 if len(os.Args) > 1 && os.Args[1] == "decode" { 29 return Decode() 30 } ··· 80 startCutoff = &time.Time{} 81 } 82 if endDuration == 0 { 83 + t := time.Unix(math.MaxInt64/2, 0) // i had it set to max but there were rollover issues 84 endCutoff = &t 85 } 86 included := 0 ··· 106 } 107 continue 108 } 109 + // fmt.Printf("ev.Time: %+v, startCutoff: %+v, endCutoff: %+v\n", ev.Time, *startCutoff, *endCutoff) 110 + if ev.Time.Before(*startCutoff) { 111 dropped++ 112 continue 113 } ··· 122 func Decode() error { 123 var path string 124 flag.StringVar(&path, "path", "", "path to the file to decode") 125 + err := flag.CommandLine.Parse(os.Args[2:]) 126 + if err != nil { 127 + return err 128 + } 129 if path == "" { 130 return fmt.Errorf("path is required") 131 }
+2 -1
rust/iroh-streamplace/Cargo.toml
··· 25 postcard = { version = "1.1.3", features = ["use-std"] } 26 irpc = "0.9.0" 27 irpc-iroh = "0.9.0" 28 - c2pa = { git = "https://github.com/hyphacoop/c2pa-rs.git", rev = "1b84d40219b27340a30fc309250e774e8a7b7761", features = [ 29 "openssl", 30 "file_io", 31 ] } ··· 36 iroh-gossip = "0.93.1" 37 ref-cast = "1.0.25" 38 rand = "0.9" 39 40 [dev-dependencies] 41 testresult = "0.4.1"
··· 25 postcard = { version = "1.1.3", features = ["use-std"] } 26 irpc = "0.9.0" 27 irpc-iroh = "0.9.0" 28 + c2pa = { git = "https://github.com/streamplace/c2pa-rs.git", rev = "544825abfaf9e588813e18dba70c8d5afd039d46", features = [ 29 "openssl", 30 "file_io", 31 ] } ··· 36 iroh-gossip = "0.93.1" 37 ref-cast = "1.0.25" 38 rand = "0.9" 39 + base64 = "0.22.1" 40 41 [dev-dependencies] 42 testresult = "0.4.1"
+170 -18
rust/iroh-streamplace/src/c2pa.rs
··· 1 use std::{io::Cursor, sync::Arc}; 2 3 - use c2pa::{Builder, CallbackSigner, Reader, settings::Settings}; 4 - use serde_json; 5 6 - #[derive(Debug, thiserror::Error, uniffi::Error)] 7 - #[uniffi(flat_error)] 8 - pub enum SPError { 9 - #[error("No certificate chain found")] 10 - NoCertificateChainFound, 11 - #[error("C2PA error: {0}")] 12 - C2paError(String), 13 - } 14 15 #[uniffi::export] 16 - pub fn get_manifest_and_cert(data: Vec<u8>) -> Result<String, SPError> { 17 - let reader = Reader::from_stream("video/mp4", Cursor::new(data)) 18 .map_err(|e| SPError::C2paError(e.to_string()))?; 19 if let Some(manifest) = reader.active_manifest() { 20 let cert_chain = if let Some(si) = manifest.signature_info() { 21 si.cert_chain() ··· 25 26 let result = serde_json::json!({ 27 "manifest": manifest, 28 - "cert": cert_chain 29 }); 30 31 return Ok(result.to_string()); ··· 103 #[uniffi::export] 104 pub fn sign( 105 manifest: String, 106 - data: Vec<u8>, 107 - certs: Vec<u8>, 108 gosigner: Arc<dyn GoSigner>, 109 ) -> Result<Vec<u8>, SPError> { 110 Settings::from_toml(TOML_SETTINGS).map_err(|e| SPError::C2paError(e.to_string()))?; 111 let callback_signer = CallbackSigner::new( 112 move |_context: *const (), data: &[u8]| { 113 - gosigner 114 .sign(data.to_vec()) 115 - .map_err(|e| c2pa::Error::BadParam(e.to_string())) 116 }, 117 c2pa::SigningAlg::Es256K, 118 certs, ··· 120 let mut builder = 121 Builder::from_json(&manifest).map_err(|e| SPError::C2paError(e.to_string()))?; 122 let mut output = Vec::new(); 123 - let mut input_cursor = Cursor::new(data); 124 let mut output_cursor = Cursor::new(&mut output); 125 builder 126 .sign( ··· 132 .map_err(|e| SPError::C2paError(e.to_string()))?; 133 Ok(output) 134 }
··· 1 use std::{io::Cursor, sync::Arc}; 2 3 + use c2pa::Builder; 4 + use c2pa::CallbackSigner; 5 + use c2pa::Reader; 6 + use c2pa::settings::Settings; 7 8 + use c2pa::Ingredient; 9 + use c2pa::jumbf_io; 10 + use c2pa::status_tracker::StatusTracker; 11 + use c2pa::store::Store; 12 13 + use crate::streams::ManyStreams; 14 + use crate::streams::Stream; 15 + use crate::streams::StreamAdapter; 16 + use serde_json; 17 + 18 + use crate::error::SPError; 19 + use base64::{Engine as _, engine::general_purpose::STANDARD}; 20 #[uniffi::export] 21 + pub fn get_manifest_and_cert(data: &dyn Stream) -> Result<String, SPError> { 22 + let reader = Reader::from_stream("video/mp4", StreamAdapter::from(data)) 23 .map_err(|e| SPError::C2paError(e.to_string()))?; 24 + 25 if let Some(manifest) = reader.active_manifest() { 26 let cert_chain = if let Some(si) = manifest.signature_info() { 27 si.cert_chain() ··· 31 32 let result = serde_json::json!({ 33 "manifest": manifest, 34 + "cert": cert_chain, 35 + "validation_results": reader.validation_results(), 36 + "validation_state": reader.validation_state(), 37 }); 38 39 return Ok(result.to_string()); ··· 111 #[uniffi::export] 112 pub fn sign( 113 manifest: String, 114 + data: &dyn Stream, 115 + certs_str: String, 116 gosigner: Arc<dyn GoSigner>, 117 ) -> Result<Vec<u8>, SPError> { 118 Settings::from_toml(TOML_SETTINGS).map_err(|e| SPError::C2paError(e.to_string()))?; 119 + let certs = STANDARD 120 + .decode(certs_str) 121 + .map_err(|e| SPError::C2paError(e.to_string()))?; 122 let callback_signer = CallbackSigner::new( 123 move |_context: *const (), data: &[u8]| { 124 + let signature = gosigner 125 .sign(data.to_vec()) 126 + .map_err(|e| c2pa::Error::BadParam(e.to_string()))?; 127 + Ok(signature) 128 }, 129 c2pa::SigningAlg::Es256K, 130 certs, ··· 132 let mut builder = 133 Builder::from_json(&manifest).map_err(|e| SPError::C2paError(e.to_string()))?; 134 let mut output = Vec::new(); 135 + let mut input_cursor = StreamAdapter::from(data); 136 let mut output_cursor = Cursor::new(&mut output); 137 builder 138 .sign( ··· 144 .map_err(|e| SPError::C2paError(e.to_string()))?; 145 Ok(output) 146 } 147 + 148 + #[uniffi::export] 149 + pub fn get_manifests(data: &dyn Stream) -> Result<String, SPError> { 150 + let store = Reader::from_stream("video/mp4", StreamAdapter::from(data)) 151 + .map_err(|e| SPError::C2paError(e.to_string()))?; 152 + let mut certs: std::collections::HashMap<String, String> = std::collections::HashMap::new(); 153 + for (label, manifest) in store.manifests() { 154 + let cert_chain = manifest 155 + .signature_info() 156 + .ok_or(SPError::C2paError(format!( 157 + "No signature info for manifest: {}", 158 + label 159 + )))? 160 + .cert_chain(); 161 + certs.insert(label.clone(), cert_chain.to_string()); 162 + } 163 + let result = serde_json::json!({ 164 + "manifests": store.manifests(), 165 + "certs": certs 166 + }); 167 + Ok(result.to_string()) 168 + } 169 + 170 + #[uniffi::export(with_foreign)] 171 + pub trait SegmentToSign: Send + Sync { 172 + fn unsigned_seg_stream(&self) -> Arc<dyn Stream>; 173 + fn manifest_id(&self) -> String; 174 + fn cert(&self) -> Vec<u8>; 175 + fn output_seg_stream(&self) -> Arc<dyn Stream>; 176 + fn close(&self); 177 + } 178 + 179 + #[uniffi::export(with_foreign)] 180 + pub trait ManySegmentsToSign: Send + Sync { 181 + fn next(&self) -> Option<Arc<dyn SegmentToSign>>; 182 + } 183 + 184 + #[uniffi::export] 185 + pub fn resign( 186 + segs_to_sign: &dyn ManySegmentsToSign, 187 + signed_concat_data: &dyn Stream, 188 + ) -> Result<(), SPError> { 189 + let mut validation_log = StatusTracker::default(); 190 + 191 + let combined_store = Store::from_stream( 192 + "video/mp4", 193 + StreamAdapter::from(signed_concat_data), 194 + true, 195 + &mut validation_log, 196 + ) 197 + .map_err(|e| SPError::C2paError(format!("from_stream failed: {}", e)))?; 198 + 199 + while let Some(seg) = segs_to_sign.next() { 200 + let unsigned_seg_data_stream = seg.unsigned_seg_stream(); 201 + let manifest_id = seg.manifest_id(); 202 + let cert = seg.cert(); 203 + let output_stream = seg.output_seg_stream(); 204 + let mut input_cursor = StreamAdapter::from(&*unsigned_seg_data_stream); 205 + let mut output_cursor = StreamAdapter::from(&*output_stream); 206 + 207 + // let mut output = Vec::<u8>::new(); 208 + let seg_claim = 209 + combined_store 210 + .get_claim(manifest_id.as_str()) 211 + .ok_or(SPError::C2paError(format!( 212 + "Segment claim not found: {}", 213 + manifest_id 214 + )))?; 215 + let signature_val = seg_claim.signature_val().clone(); 216 + 217 + let callback_signer = CallbackSigner::new( 218 + move |_context: *const (), _data: &[u8]| Ok(signature_val.clone()), 219 + c2pa::SigningAlg::Es256K, 220 + cert, 221 + ); 222 + 223 + let mut seg_store = Store::new(); 224 + let _provenance = seg_store 225 + .commit_claim(seg_claim.clone()) 226 + .map_err(|e| SPError::C2paError(format!("commit_claim failed: {}", e)))?; 227 + 228 + let jumbf_bytes = seg_store 229 + .to_jumbf(&callback_signer) 230 + .map_err(|e| SPError::C2paError(format!("to_jumbf failed: {}", e)))?; 231 + 232 + jumbf_io::save_jumbf_to_stream( 233 + "video/mp4", 234 + &mut input_cursor, 235 + &mut output_cursor, 236 + &jumbf_bytes, 237 + ) 238 + .map_err(|e| SPError::C2paError(format!("save_jumbf_to_stream failed: {}", e)))?; 239 + seg.close(); 240 + } 241 + 242 + Ok(()) 243 + } 244 + 245 + #[uniffi::export] 246 + pub fn sign_with_ingredients( 247 + manifest: String, 248 + data: &dyn Stream, 249 + certs_str: String, 250 + ingredients: &dyn ManyStreams, 251 + gosigner: Arc<dyn GoSigner>, 252 + output: &dyn Stream, 253 + ) -> Result<(), SPError> { 254 + let certs = STANDARD 255 + .decode(certs_str) 256 + .map_err(|e| SPError::C2paError(e.to_string()))?; 257 + Settings::from_toml(TOML_SETTINGS).map_err(|e| SPError::C2paError(e.to_string()))?; 258 + let callback_signer = CallbackSigner::new( 259 + move |_context: *const (), data: &[u8]| { 260 + gosigner 261 + .sign(data.to_vec()) 262 + .map_err(|e| c2pa::Error::BadParam(e.to_string())) 263 + }, 264 + c2pa::SigningAlg::Es256K, 265 + certs, 266 + ); 267 + let mut builder = 268 + Builder::from_json(&manifest).map_err(|e| SPError::C2paError(e.to_string()))?; 269 + while let Some(ingredient) = ingredients.next() { 270 + let mut cursor = StreamAdapter::from(&*ingredient); 271 + let ingredient = Ingredient::from_stream("video/mp4", &mut cursor) 272 + .map_err(|e| SPError::C2paError(e.to_string()))?; 273 + builder.add_ingredient(ingredient); 274 + } 275 + let mut input_cursor = StreamAdapter::from(data); 276 + let mut output_cursor = StreamAdapter::from(output); 277 + builder 278 + .sign( 279 + &callback_signer, 280 + "video/mp4", 281 + &mut input_cursor, 282 + &mut output_cursor, 283 + ) 284 + .map_err(|e| SPError::C2paError(e.to_string()))?; 285 + Ok(()) 286 + }
+10
rust/iroh-streamplace/src/error.rs
···
··· 1 + #[derive(Debug, thiserror::Error, uniffi::Error)] 2 + #[uniffi(flat_error)] 3 + pub enum SPError { 4 + #[error("No certificate chain found")] 5 + NoCertificateChainFound, 6 + #[error("C2PA error: {0}")] 7 + C2paError(String), 8 + #[error("IO Error: {0}")] 9 + IOError(String), 10 + }
+5
rust/iroh-streamplace/src/lib.rs
··· 1 uniffi::setup_scaffolding!(); 2 3 pub mod c2pa; 4 pub mod node_addr; 5 pub mod public_key; 6 7 use std::sync::{LazyLock, Once}; 8 ··· 10 pub use db::*; 11 #[cfg(test)] 12 mod tests; 13 14 /// Lazily initialized Tokio runtime for use in uniffi methods that need a runtime. 15 static RUNTIME: LazyLock<tokio::runtime::Runtime> =
··· 1 uniffi::setup_scaffolding!(); 2 3 pub mod c2pa; 4 + pub mod error; 5 pub mod node_addr; 6 pub mod public_key; 7 + pub mod streams; 8 9 use std::sync::{LazyLock, Once}; 10 ··· 12 pub use db::*; 13 #[cfg(test)] 14 mod tests; 15 + 16 + #[cfg(test)] 17 + mod test_stream; 18 19 /// Lazily initialized Tokio runtime for use in uniffi methods that need a runtime. 20 static RUNTIME: LazyLock<tokio::runtime::Runtime> =
+166
rust/iroh-streamplace/src/streams.rs
···
··· 1 + // Copyright 2023 Adobe. All rights reserved. 2 + // This file is licensed to you under the Apache License, 3 + // Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) 4 + // or the MIT license (http://opensource.org/licenses/MIT), 5 + // at your option. 6 + 7 + // Unless required by applicable law or agreed to in writing, 8 + // this software is distributed on an "AS IS" BASIS, WITHOUT 9 + // WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or 10 + // implied. See the LICENSE-MIT and LICENSE-APACHE files for the 11 + // specific language governing permissions and limitations under 12 + // each license. 13 + 14 + use crate::error::SPError; 15 + use std::io::{Read, Seek, SeekFrom, Write}; 16 + use std::sync::Arc; 17 + 18 + // #[repr(C)] 19 + // #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 20 + // pub enum SeekMode { 21 + // Start = 0, 22 + // End = 1, 23 + // Current = 2, 24 + // } 25 + 26 + /// This allows for a callback stream over the Uniffi interface. 27 + /// Implement these stream functions in the foreign language 28 + /// and this will provide Rust Stream trait implementations 29 + /// This is necessary since the Rust traits cannot be implemented directly 30 + /// as uniffi callbacks 31 + #[uniffi::export(with_foreign)] 32 + pub trait Stream: Send + Sync { 33 + /// Read a stream of bytes from the stream 34 + fn read_stream(&self, length: u64) -> Result<Vec<u8>, SPError>; 35 + /// Seek to a position in the stream 36 + fn seek_stream(&self, pos: i64, mode: u64) -> Result<u64, SPError>; 37 + /// Write a stream of bytes to the stream 38 + fn write_stream(&self, data: Vec<u8>) -> Result<u64, SPError>; 39 + } 40 + 41 + impl Stream for Arc<dyn Stream> { 42 + fn read_stream(&self, length: u64) -> Result<Vec<u8>, SPError> { 43 + (**self).read_stream(length) 44 + } 45 + 46 + fn seek_stream(&self, pos: i64, mode: u64) -> Result<u64, SPError> { 47 + (**self).seek_stream(pos, mode) 48 + } 49 + 50 + fn write_stream(&self, data: Vec<u8>) -> Result<u64, SPError> { 51 + (**self).write_stream(data) 52 + } 53 + } 54 + 55 + impl AsMut<dyn Stream> for dyn Stream { 56 + fn as_mut(&mut self) -> &mut Self { 57 + self 58 + } 59 + } 60 + 61 + pub struct StreamAdapter<'a> { 62 + pub stream: &'a dyn Stream, 63 + } 64 + 65 + impl<'a> StreamAdapter<'a> { 66 + pub fn from_stream_mut(stream: &'a mut dyn Stream) -> Self { 67 + Self { stream } 68 + } 69 + } 70 + 71 + impl<'a> From<&'a dyn Stream> for StreamAdapter<'a> { 72 + fn from(stream: &'a dyn Stream) -> Self { 73 + Self { stream } 74 + } 75 + } 76 + 77 + impl<'a> Read for StreamAdapter<'a> { 78 + fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { 79 + let mut bytes = self 80 + .stream 81 + .read_stream(buf.len() as u64) 82 + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; 83 + let len = bytes.len(); 84 + buf.iter_mut().zip(bytes.drain(..)).for_each(|(dest, src)| { 85 + *dest = src; 86 + }); 87 + //println!("read: {:?}", len); 88 + Ok(len) 89 + } 90 + } 91 + 92 + impl<'a> Seek for StreamAdapter<'a> { 93 + fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> { 94 + let (pos, mode) = match pos { 95 + SeekFrom::Current(pos) => (pos, 2), 96 + SeekFrom::Start(pos) => (pos as i64, 0), 97 + SeekFrom::End(pos) => (pos, 1), 98 + }; 99 + //println!("Stream Seek {}", pos); 100 + self.stream 101 + .seek_stream(pos, mode) 102 + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) 103 + } 104 + } 105 + 106 + impl<'a> Write for StreamAdapter<'a> { 107 + fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { 108 + let len = self 109 + .stream 110 + .write_stream(buf.to_vec()) 111 + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; 112 + Ok(len as usize) 113 + } 114 + 115 + fn flush(&mut self) -> std::io::Result<()> { 116 + Ok(()) 117 + } 118 + } 119 + 120 + #[uniffi::export(with_foreign)] 121 + pub trait ManyStreams: Send + Sync { 122 + /// Get the next stream from the many streams 123 + fn next(&self) -> Option<Arc<dyn Stream>>; 124 + } 125 + 126 + #[cfg(test)] 127 + mod tests { 128 + use super::*; 129 + 130 + use crate::test_stream::TestStream; 131 + 132 + #[test] 133 + fn test_stream_read() { 134 + let mut test = TestStream::from_memory(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); 135 + let mut stream = StreamAdapter::from_stream_mut(&mut test); 136 + let mut buf = [0u8; 5]; 137 + let len = stream.read(&mut buf).unwrap(); 138 + assert_eq!(len, 5); 139 + assert_eq!(buf, [0, 1, 2, 3, 4]); 140 + } 141 + 142 + #[test] 143 + fn test_stream_seek() { 144 + let mut test = TestStream::from_memory(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); 145 + let mut stream = StreamAdapter { stream: &mut test }; 146 + let pos = stream.seek(SeekFrom::Start(5)).unwrap(); 147 + assert_eq!(pos, 5); 148 + let mut buf = [0u8; 5]; 149 + let len = stream.read(&mut buf).unwrap(); 150 + assert_eq!(len, 5); 151 + assert_eq!(buf, [5, 6, 7, 8, 9]); 152 + } 153 + 154 + #[test] 155 + fn test_stream_write() { 156 + let mut test = TestStream::new(); 157 + let mut stream = StreamAdapter { stream: &mut test }; 158 + let len = stream.write(&[0, 1, 2, 3, 4]).unwrap(); 159 + assert_eq!(len, 5); 160 + stream.seek(SeekFrom::Start(0)).unwrap(); 161 + let mut buf = [0u8; 5]; 162 + let len = stream.read(&mut buf).unwrap(); 163 + assert_eq!(len, 5); 164 + assert_eq!(buf, [0, 1, 2, 3, 4]); 165 + } 166 + }
+80
rust/iroh-streamplace/src/test_stream.rs
···
··· 1 + // Copyright 2023 Adobe. All rights reserved. 2 + // This file is licensed to you under the Apache License, 3 + // Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) 4 + // or the MIT license (http://opensource.org/licenses/MIT), 5 + // at your option. 6 + 7 + // Unless required by applicable law or agreed to in writing, 8 + // this software is distributed on an "AS IS" BASIS, WITHOUT 9 + // WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or 10 + // implied. See the LICENSE-MIT and LICENSE-APACHE files for the 11 + // specific language governing permissions and limitations under 12 + // each license. 13 + 14 + use std::io::{Read, Seek, SeekFrom, Write}; 15 + use std::sync::RwLock; 16 + 17 + use crate::error::SPError; 18 + use crate::streams::Stream; 19 + use std::io::Cursor; 20 + 21 + pub struct TestStream { 22 + stream: RwLock<Cursor<Vec<u8>>>, 23 + } 24 + 25 + impl TestStream { 26 + pub fn new() -> Self { 27 + Self { 28 + stream: RwLock::new(Cursor::new(Vec::new())), 29 + } 30 + } 31 + pub fn from_memory(data: Vec<u8>) -> Self { 32 + Self { 33 + stream: RwLock::new(Cursor::new(data)), 34 + } 35 + } 36 + } 37 + 38 + impl Stream for TestStream { 39 + fn read_stream(&self, length: u64) -> Result<Vec<u8>, SPError> { 40 + if let Ok(mut stream) = RwLock::write(&self.stream) { 41 + let mut data = vec![0u8; length as usize]; 42 + let bytes_read = stream 43 + .read(&mut data) 44 + .map_err(|e| SPError::IOError(e.to_string()))?; 45 + data.truncate(bytes_read); 46 + //println!("read_stream: {:?}, pos {:?}", data.len(), (*stream).position()); 47 + Ok(data) 48 + } else { 49 + Err(SPError::IOError("RwLock".to_string()))? 50 + } 51 + } 52 + 53 + fn seek_stream(&self, pos: i64, mode: u64) -> Result<u64, SPError> { 54 + if let Ok(mut stream) = RwLock::write(&self.stream) { 55 + //stream.seek(SeekFrom::Start(pos as u64)).map_err(|e| StreamError::Io{ reason: e.to_string()})?; 56 + let whence = match mode { 57 + 0 => SeekFrom::Start(pos as u64), 58 + 1 => SeekFrom::End(pos as i64), 59 + 2 => SeekFrom::Current(pos as i64), 60 + 3_u64..=u64::MAX => unimplemented!(), 61 + }; 62 + Ok(stream 63 + .seek(whence) 64 + .map_err(|e| SPError::IOError(e.to_string()))?) 65 + } else { 66 + Err(SPError::IOError("RwLock".to_string())) 67 + } 68 + } 69 + 70 + fn write_stream(&self, data: Vec<u8>) -> Result<u64, SPError> { 71 + if let Ok(mut stream) = RwLock::write(&self.stream) { 72 + let len = stream 73 + .write(&data) 74 + .map_err(|e| SPError::IOError(e.to_string()))?; 75 + Ok(len as u64) 76 + } else { 77 + Err(SPError::IOError("RwLock".to_string()))? 78 + } 79 + } 80 + }
+1 -1
subprojects/gstreamer-full.wrap
··· 1 [wrap-git] 2 url = https://gitlab.freedesktop.org/iameli/gstreamer.git 3 - revision = 5c372f9595b7024d1074e45ee50b68b7a9a3f5e3 4 depth = 1
··· 1 [wrap-git] 2 url = https://gitlab.freedesktop.org/iameli/gstreamer.git 3 + revision = 587f2f9d8678df804515dac40aaac0a732c32c00 4 depth = 1
+67
test/remote/remote-fixtures.go
··· 1 package remote 2 3 import ( 4 "crypto/sha256" 5 "encoding/hex" 6 "fmt" ··· 80 81 return finalPath 82 }
··· 1 package remote 2 3 import ( 4 + "archive/tar" 5 + "compress/gzip" 6 "crypto/sha256" 7 "encoding/hex" 8 "fmt" ··· 82 83 return finalPath 84 } 85 + 86 + // takes a tarball, returns a directory with the contents 87 + func RemoteArchive(name string) string { 88 + fpath := RemoteFixture(name) 89 + 90 + // Create extracted directory adjacent to the archive file 91 + dir := filepath.Dir(fpath) 92 + extractedDir := filepath.Join(dir, "extracted") 93 + 94 + if err := os.MkdirAll(extractedDir, 0755); err != nil { 95 + panic(err) 96 + } 97 + 98 + // Extract the tarball contents into the directory 99 + file, err := os.Open(fpath) 100 + if err != nil { 101 + panic(err) 102 + } 103 + defer file.Close() 104 + 105 + // Create gzip reader 106 + gzr, err := gzip.NewReader(file) 107 + if err != nil { 108 + panic(err) 109 + } 110 + defer gzr.Close() 111 + 112 + tr := tar.NewReader(gzr) 113 + for { 114 + header, err := tr.Next() 115 + if err == io.EOF { 116 + break 117 + } 118 + if err != nil { 119 + panic(err) 120 + } 121 + 122 + target := filepath.Join(extractedDir, header.Name) 123 + 124 + switch header.Typeflag { 125 + case tar.TypeDir: 126 + if err := os.MkdirAll(target, 0755); err != nil { 127 + panic(err) 128 + } 129 + case tar.TypeReg: 130 + // Create parent directories if needed 131 + if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil { 132 + panic(err) 133 + } 134 + 135 + outFile, err := os.Create(target) 136 + if err != nil { 137 + panic(err) 138 + } 139 + 140 + if _, err := io.Copy(outFile, tr); err != nil { 141 + outFile.Close() 142 + panic(err) 143 + } 144 + outFile.Close() 145 + } 146 + } 147 + 148 + return extractedDir 149 + }