bee/gsoc/mod.rs
1//! GSOC send / subscribe + offline `soc_address`. Mirrors `pkg/gsoc`
2//! in bee-go and bee-js `Bee.gsocSend` / `gsocSubscribe`.
3//!
4//! GSOC piggy-backs on the SOC primitive: a signer is "mined" (see
5//! [`crate::swarm::gsoc_mine`]) so the SOC address
6//! `keccak256(identifier || owner)` lands inside the target node's
7//! neighbourhood. The target subscribes to
8//! `/gsoc/subscribe/{soc_address}` and receives any chunks that hash
9//! to that address.
10
11use std::sync::Arc;
12
13use bytes::Bytes;
14
15use crate::api::{UploadOptions, UploadResult};
16use crate::client::Inner;
17use crate::pss::Subscription;
18use crate::swarm::{
19 BatchId, Error, EthAddress, Identifier, PrivateKey, Reference, SPAN_LENGTH,
20 calculate_single_owner_chunk_address, make_single_owner_chunk,
21};
22
23/// Handle exposing the GSOC endpoints. Cheap to clone.
24#[derive(Clone, Debug)]
25pub struct GsocApi {
26 pub(crate) inner: Arc<Inner>,
27}
28
29impl GsocApi {
30 pub(crate) fn new(inner: Arc<Inner>) -> Self {
31 Self { inner }
32 }
33
34 /// Build a SOC at `(identifier, signer)` carrying `data` as the
35 /// payload, then upload via `POST /soc/{owner}/{id}` (mirrors
36 /// bee-js `gsoc.send` / bee-go `gsoc.Service.Send`).
37 ///
38 /// Use [`crate::swarm::gsoc_mine`] to derive `signer` so the SOC
39 /// lands in the target's neighbourhood. Returns the upload
40 /// result; `reference` is the SOC address.
41 pub async fn send(
42 &self,
43 batch_id: &BatchId,
44 signer: &PrivateKey,
45 identifier: &Identifier,
46 data: &[u8],
47 opts: Option<&UploadOptions>,
48 ) -> Result<UploadResult, Error> {
49 let soc = make_single_owner_chunk(identifier, data, signer)?;
50 // Wire body for `/soc/{owner}/{id}`: `span (8) || payload`.
51 let mut body = Vec::with_capacity(SPAN_LENGTH + soc.payload.len());
52 body.extend_from_slice(soc.span.as_bytes());
53 body.extend_from_slice(&soc.payload);
54
55 let owner = signer.public_key()?.address();
56 let file_api = crate::file::FileApi::new(self.inner.clone());
57 file_api
58 .upload_soc(
59 batch_id,
60 &owner,
61 identifier,
62 &soc.signature,
63 Bytes::from(body),
64 opts,
65 )
66 .await
67 }
68
69 /// Subscribe to `/gsoc/subscribe/{soc_address}` over a websocket.
70 /// `soc_address = keccak256(identifier || owner)`. Returns a
71 /// [`Subscription`] yielding the raw bytes of each delivered
72 /// message (see [`Subscription::recv`]).
73 pub async fn subscribe(
74 &self,
75 owner: &EthAddress,
76 identifier: &Identifier,
77 ) -> Result<Subscription, Error> {
78 let addr = calculate_single_owner_chunk_address(identifier, owner)?;
79 let path = format!("gsoc/subscribe/{}", addr.to_hex());
80 Subscription::open(&self.inner, &path).await
81 }
82
83 /// Address GSOC subscribers listen on:
84 /// `keccak256(identifier || owner)`. Pure function exposed at
85 /// service level so callers can derive the address without
86 /// pulling in `crate::swarm`.
87 pub fn soc_address(
88 &self,
89 identifier: &Identifier,
90 owner: &EthAddress,
91 ) -> Result<Reference, Error> {
92 calculate_single_owner_chunk_address(identifier, owner)
93 }
94}