Skip to main content

bee/file/
soc.rs

1//! SOC upload, reader, writer. Mirrors bee-go's `pkg/file/soc.go`.
2
3use bytes::Bytes;
4use reqwest::Method;
5use serde::Deserialize;
6
7use crate::api::{UploadOptions, UploadResult, prepare_upload_headers};
8use crate::client::{Inner, request};
9use crate::swarm::{
10    BatchId, Error, EthAddress, Identifier, PrivateKey, Reference, Signature, SingleOwnerChunk,
11    calculate_single_owner_chunk_address, make_single_owner_chunk, unmarshal_single_owner_chunk,
12};
13
14use super::FileApi;
15use super::chunk::download_chunk_response;
16
17#[derive(Deserialize)]
18struct UploadBody {
19    reference: String,
20}
21
22impl FileApi {
23    /// Upload a Single Owner Chunk to `POST /soc/{owner}/{id}?sig=…`.
24    /// `data` must be the SOC body in wire form: `span (8) || payload`.
25    /// Mirrors bee-go `(*Service).UploadSOC`.
26    pub async fn upload_soc(
27        &self,
28        batch_id: &BatchId,
29        owner: &EthAddress,
30        identifier: &Identifier,
31        signature: &Signature,
32        data: impl Into<Bytes>,
33        opts: Option<&UploadOptions>,
34    ) -> Result<UploadResult, Error> {
35        let path = format!("soc/{}/{}", owner.to_hex(), identifier.to_hex());
36        let builder = request(&self.inner, Method::POST, &path)?
37            .header("Content-Type", "application/octet-stream")
38            .query(&[("sig", signature.to_hex())])
39            .body(data.into());
40        let builder = Inner::apply_headers(builder, prepare_upload_headers(batch_id, opts));
41        let resp = self.inner.send(builder).await?;
42        let headers = resp.headers().clone();
43        let body: UploadBody = serde_json::from_slice(&resp.bytes().await?)?;
44        UploadResult::from_response(&body.reference, &headers)
45    }
46
47    /// Construct a [`SocReader`] for the given owner.
48    pub fn make_soc_reader(&self, owner: EthAddress) -> SocReader {
49        SocReader {
50            owner,
51            inner: self.inner.clone(),
52        }
53    }
54
55    /// Construct a [`SocWriter`] for the given signer. Owner is
56    /// derived from `signer.public_key().address()`.
57    pub fn make_soc_writer(&self, signer: PrivateKey) -> Result<SocWriter, Error> {
58        let owner = signer.public_key()?.address();
59        Ok(SocWriter {
60            reader: SocReader {
61                owner,
62                inner: self.inner.clone(),
63            },
64            signer,
65        })
66    }
67}
68
69/// Reader for SOCs owned by a known [`EthAddress`]. Fetches each
70/// chunk by computing its `keccak256(identifier || owner)` address
71/// and verifying the recovered signer matches the owner.
72#[derive(Clone, Debug)]
73pub struct SocReader {
74    owner: EthAddress,
75    inner: std::sync::Arc<Inner>,
76}
77
78impl SocReader {
79    /// Address whose SOCs this reader downloads.
80    pub fn owner(&self) -> &EthAddress {
81        &self.owner
82    }
83
84    /// Fetch the SOC at `keccak256(identifier || owner)`, parse the
85    /// wire form, and verify it.
86    pub async fn download(&self, identifier: &Identifier) -> Result<SingleOwnerChunk, Error> {
87        let address = calculate_single_owner_chunk_address(identifier, &self.owner)?;
88        let resp = download_chunk_response(&self.inner, &address, None).await?;
89        let bytes = resp.bytes().await?;
90        unmarshal_single_owner_chunk(&bytes, &address)
91    }
92}
93
94/// Reader + writer for SOCs signed by a known [`PrivateKey`].
95#[derive(Clone, Debug)]
96pub struct SocWriter {
97    reader: SocReader,
98    signer: PrivateKey,
99}
100
101impl SocWriter {
102    /// Owner address derived from the signer.
103    pub fn owner(&self) -> &EthAddress {
104        self.reader.owner()
105    }
106
107    /// Read side of this writer.
108    pub fn reader(&self) -> &SocReader {
109        &self.reader
110    }
111
112    /// Sign and upload a SOC for `identifier` with `data`. Returns
113    /// the SOC reference (`= keccak256(identifier || owner)`).
114    pub async fn upload(
115        &self,
116        batch_id: &BatchId,
117        identifier: &Identifier,
118        data: &[u8],
119        opts: Option<&UploadOptions>,
120    ) -> Result<UploadResult, Error> {
121        let soc = make_single_owner_chunk(identifier, data, &self.signer)?;
122        let mut full = Vec::with_capacity(soc.span.as_bytes().len() + soc.payload.len());
123        full.extend_from_slice(soc.span.as_bytes());
124        full.extend_from_slice(&soc.payload);
125        let api = FileApi {
126            inner: self.reader.inner.clone(),
127        };
128        api.upload_soc(
129            batch_id,
130            &self.reader.owner,
131            identifier,
132            &soc.signature,
133            full,
134            opts,
135        )
136        .await
137    }
138}
139
140/// Reference to a SOC chunk: `keccak256(identifier || owner)`.
141/// Re-exported convenience wrapper over the swarm-level helper.
142pub fn soc_address(identifier: &Identifier, owner: &EthAddress) -> Result<Reference, Error> {
143    calculate_single_owner_chunk_address(identifier, owner)
144}