Skip to main content

nv_redfish_core/
upload.rs

1// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use core::pin::Pin;
17use std::error::Error as StdError;
18use std::fmt;
19use std::time::Duration;
20
21use futures_io::AsyncRead;
22
23const OEM_PREFIX: &str = "Oem";
24
25/// Async reader accepted by upload methods.
26pub trait UploadReader: AsyncRead + Send + 'static {}
27
28impl<T> UploadReader for T where T: AsyncRead + Send + 'static {}
29
30/// Named data stream accepted by multipart upload methods.
31pub struct DataStream<R> {
32    /// Multipart filename for this stream.
33    pub name: String,
34
35    /// Streamed upload data.
36    pub reader: R,
37
38    /// Known stream length, when available.
39    pub content_length: Option<u64>,
40}
41
42impl<R> DataStream<R> {
43    /// Create a named data stream without a known content length.
44    #[must_use]
45    pub fn new(name: impl Into<String>, reader: R) -> Self {
46        Self {
47            name: name.into(),
48            reader,
49            content_length: None,
50        }
51    }
52
53    /// Attach a known content length.
54    #[must_use]
55    pub const fn with_content_length(mut self, content_length: u64) -> Self {
56        self.content_length = Some(content_length);
57        self
58    }
59}
60
61/// Streamed body accepted by deprecated raw upload methods.
62#[cfg(feature = "update-service-deprecated")]
63pub struct UploadStream<R> {
64    /// Streamed upload data.
65    pub reader: R,
66
67    /// Known stream length, when available.
68    pub content_length: Option<u64>,
69}
70
71#[cfg(feature = "update-service-deprecated")]
72impl<R> UploadStream<R> {
73    /// Create a streamed upload body without a known content length.
74    #[must_use]
75    pub const fn new(reader: R) -> Self {
76        Self {
77            reader,
78            content_length: None,
79        }
80    }
81
82    /// Attach a known content length.
83    #[must_use]
84    pub const fn with_content_length(mut self, content_length: u64) -> Self {
85        self.content_length = Some(content_length);
86        self
87    }
88}
89
90/// Error returned when an OEM multipart part name is invalid.
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct OemMultipartPartNameError {
93    /// Invalid multipart part name.
94    pub name: String,
95}
96
97impl fmt::Display for OemMultipartPartNameError {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        write!(
100            f,
101            "OEM multipart part name must start with Oem: {}",
102            self.name
103        )
104    }
105}
106
107impl StdError for OemMultipartPartNameError {}
108
109/// Reader type used for OEM multipart form parts.
110pub type OemMultipartPartReader = Pin<Box<dyn AsyncRead + Send + 'static>>;
111
112/// OEM multipart form part.
113pub struct OemMultipartPart {
114    /// Multipart part name.
115    pub name: String,
116
117    /// Streamed part data.
118    pub reader: OemMultipartPartReader,
119
120    /// Optional part content type.
121    pub content_type: Option<String>,
122
123    /// Known part length, when available.
124    pub content_length: Option<u64>,
125}
126
127impl OemMultipartPart {
128    /// Checks if the OEM part name is valid per the spec.
129    #[must_use]
130    pub fn is_name_valid(&self) -> bool {
131        self.name.starts_with(OEM_PREFIX)
132    }
133}
134
135impl OemMultipartPart {
136    /// Create an OEM multipart part.
137    ///
138    /// # Errors
139    ///
140    /// Returns an error if `name` does not start with `Oem`.
141    pub fn new(
142        name: impl Into<String>,
143        reader: impl UploadReader,
144    ) -> Result<Self, OemMultipartPartNameError> {
145        let name = name.into();
146
147        if !name.starts_with(OEM_PREFIX) {
148            return Err(OemMultipartPartNameError { name });
149        }
150
151        Ok(Self {
152            name,
153            reader: Box::pin(reader),
154            content_type: None,
155            content_length: None,
156        })
157    }
158
159    /// Attach a content type.
160    #[must_use]
161    pub fn with_content_type(mut self, content_type: impl Into<String>) -> Self {
162        self.content_type = Some(content_type.into());
163        self
164    }
165
166    /// Attach a known content length.
167    #[must_use]
168    pub const fn with_content_length(mut self, content_length: u64) -> Self {
169        self.content_length = Some(content_length);
170        self
171    }
172}
173
174/// Multipart `UpdateService` upload request data.
175pub struct MultipartUpdateRequest<'a, U, V> {
176    /// Redfish `UpdateParameters` JSON part.
177    pub update_parameters: &'a V,
178
179    /// Named stream sent as the Redfish `UpdateFile` part.
180    pub update_stream: DataStream<U>,
181
182    /// Optional OEM-defined multipart parts.
183    pub oem_parts: Vec<OemMultipartPart>,
184
185    /// Timeout used only for this upload request.
186    pub upload_timeout: Duration,
187}
188
189/// `UpdateService` raw `HttpPushUri` upload request data.
190#[cfg(feature = "update-service-deprecated")]
191pub struct HttpPushUriUpdateRequest<U> {
192    /// Streamed update image data.
193    pub update_stream: UploadStream<U>,
194
195    /// Timeout used only for this upload request.
196    pub upload_timeout: Duration,
197}