Skip to main content

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}