use crate::auth::credentials::CredentialsProvider;
use crate::auth::signing_config::{SigningConfig, SigningConfigInner};
use crate::common::allocator::Allocator;
use crate::common::error::Error;
use crate::common::uri::Uri;
use crate::http::request_response::{Headers, Message};
use crate::io::channel_bootstrap::ClientBootstrap;
use crate::io::retry_strategy::RetryStrategy;
use crate::s3::s3_library_init;
use crate::{aws_byte_cursor_as_slice, CrtError, ResultExt, ToAwsByteCursor};
use mountpoint_s3_crt_sys::*;
use std::ffi::{OsStr, OsString};
use std::fmt::Debug;
use std::marker::PhantomPinned;
use std::os::unix::prelude::OsStrExt;
use std::pin::Pin;
use std::ptr::NonNull;
use std::sync::Arc;
use std::time::Duration;
#[derive(Debug)]
pub struct Client {
inner: NonNull<aws_s3_client>,
config: ClientConfig,
}
unsafe impl Send for Client {}
unsafe impl Sync for Client {}
#[derive(Debug, Default)]
pub struct ClientConfig {
inner: aws_s3_client_config,
client_bootstrap: Option<ClientBootstrap>,
signing_config: Option<SigningConfig>,
retry_strategy: Option<RetryStrategy>,
}
impl ClientConfig {
pub fn new() -> Self {
Default::default()
}
pub fn signing_config(&mut self, signing_config: SigningConfig) -> &mut Self {
self.inner.signing_config = signing_config.to_inner_ptr() as *mut aws_signing_config_aws;
self.signing_config = Some(signing_config);
self
}
pub fn client_bootstrap(&mut self, client_bootstrap: ClientBootstrap) -> &mut Self {
self.inner.client_bootstrap = client_bootstrap.inner.as_ptr();
self.client_bootstrap = Some(client_bootstrap);
self
}
pub fn retry_strategy(&mut self, retry_strategy: RetryStrategy) -> &mut Self {
self.inner.retry_strategy = retry_strategy.inner.as_ptr();
self.retry_strategy = Some(retry_strategy);
self
}
pub fn part_size(&mut self, part_size: usize) -> &mut Self {
self.inner.part_size = part_size as u64;
self
}
pub fn max_part_size(&mut self, max_part_size: usize) -> &mut Self {
self.inner.max_part_size = max_part_size as u64;
self
}
pub fn throughput_target_gbps(&mut self, throughput_target_gbps: f64) -> &mut Self {
self.inner.throughput_target_gbps = throughput_target_gbps;
self
}
pub fn max_active_connections_override(&mut self, max_active_connections_override: u32) -> &mut Self {
self.inner.max_active_connections_override = max_active_connections_override;
self
}
}
type TelemetryCallback = Box<dyn Fn(&RequestMetrics) + Send>;
type HeadersCallback = Box<dyn FnMut(&Headers, i32) + Send>;
type BodyCallback = Box<dyn FnMut(u64, &[u8]) + Send>;
type FinishCallback = Box<dyn FnOnce(MetaRequestResult) + Send>;
struct MetaRequestOptionsInner {
inner: aws_s3_meta_request_options,
message: Option<Message>,
endpoint: Option<Uri>,
signing_config: Option<SigningConfig>,
on_telemetry: Option<TelemetryCallback>,
on_headers: Option<HeadersCallback>,
on_body: Option<BodyCallback>,
on_finish: Option<FinishCallback>,
_pinned: PhantomPinned,
}
impl<'a> MetaRequestOptionsInner {
unsafe fn from_user_data_ptr(user_data: *mut libc::c_void) -> &'a mut Self {
(user_data as *mut Self).as_mut().unwrap()
}
unsafe fn from_user_data_ptr_owned(user_data: *mut libc::c_void) -> Box<Self> {
Box::from_raw(user_data as *mut Self)
}
}
impl Debug for MetaRequestOptionsInner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MetaRequestOptionsInner")
.field("inner", &self.inner)
.field("message", &self.message)
.field("signing_config", &self.signing_config)
.finish()
}
}
#[derive(Debug)]
pub struct MetaRequestOptions(Pin<Box<MetaRequestOptionsInner>>);
impl MetaRequestOptions {
pub fn new() -> Self {
let options = Box::new(MetaRequestOptionsInner {
inner: aws_s3_meta_request_options {
telemetry_callback: Some(meta_request_telemetry_callback),
headers_callback: Some(meta_request_headers_callback),
body_callback: Some(meta_request_receive_body_callback),
finish_callback: Some(meta_request_finish_callback),
shutdown_callback: Some(meta_request_shutdown_callback),
user_data: std::ptr::null_mut(), ..Default::default()
},
message: None,
endpoint: None,
signing_config: None,
on_telemetry: None,
on_headers: None,
on_body: None,
on_finish: None,
_pinned: Default::default(),
});
let mut options = Box::into_pin(options);
unsafe {
let options = Pin::get_unchecked_mut(Pin::as_mut(&mut options));
options.inner.user_data = options as *mut MetaRequestOptionsInner as *mut libc::c_void;
}
Self(options)
}
pub fn message(&mut self, message: Message) -> &mut Self {
let options = unsafe { Pin::get_unchecked_mut(Pin::as_mut(&mut self.0)) };
options.message = Some(message);
options.inner.message = options.message.as_mut().unwrap().inner.as_ptr();
if let Some(send_async_stream) = options.message.as_mut().unwrap().body_stream() {
options.inner.send_async_stream = send_async_stream.as_inner_ptr();
}
self
}
pub fn endpoint(&mut self, endpoint: Uri) -> &mut Self {
let options = unsafe { Pin::get_unchecked_mut(Pin::as_mut(&mut self.0)) };
options.endpoint = Some(endpoint);
options.inner.endpoint = options.endpoint.as_mut().unwrap().to_inner_ptr() as *mut aws_uri;
self
}
fn signing_config(&mut self, signing_config: SigningConfig) -> &mut Self {
let options = unsafe { Pin::get_unchecked_mut(Pin::as_mut(&mut self.0)) };
options.signing_config = Some(signing_config);
options.inner.signing_config =
options.signing_config.as_mut().unwrap().to_inner_ptr() as *mut aws_signing_config_aws;
self
}
pub fn on_telemetry(&mut self, callback: impl Fn(&RequestMetrics) + Send + 'static) -> &mut Self {
let options = unsafe { Pin::get_unchecked_mut(Pin::as_mut(&mut self.0)) };
options.on_telemetry = Some(Box::new(callback));
self
}
pub fn on_headers(&mut self, callback: impl FnMut(&Headers, i32) + Send + 'static) -> &mut Self {
let options = unsafe { Pin::get_unchecked_mut(Pin::as_mut(&mut self.0)) };
options.on_headers = Some(Box::new(callback));
self
}
pub fn on_body(&mut self, callback: impl FnMut(u64, &[u8]) + Send + 'static) -> &mut Self {
let options = unsafe { Pin::get_unchecked_mut(Pin::as_mut(&mut self.0)) };
options.on_body = Some(Box::new(callback));
self
}
pub fn on_finish(&mut self, callback: impl FnOnce(MetaRequestResult) + Send + 'static) -> &mut Self {
let options = unsafe { Pin::get_unchecked_mut(Pin::as_mut(&mut self.0)) };
options.on_finish = Some(Box::new(callback));
self
}
pub fn request_type(&mut self, request_type: MetaRequestType) -> &mut Self {
let options = unsafe { Pin::get_unchecked_mut(Pin::as_mut(&mut self.0)) };
options.inner.type_ = request_type.into();
self
}
}
impl Default for MetaRequestOptions {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub enum MetaRequestType {
Default,
GetObject,
PutObject,
CopyObject,
}
impl From<MetaRequestType> for aws_s3_meta_request_type {
fn from(typ: MetaRequestType) -> Self {
match typ {
MetaRequestType::Default => aws_s3_meta_request_type::AWS_S3_META_REQUEST_TYPE_DEFAULT,
MetaRequestType::GetObject => aws_s3_meta_request_type::AWS_S3_META_REQUEST_TYPE_GET_OBJECT,
MetaRequestType::PutObject => aws_s3_meta_request_type::AWS_S3_META_REQUEST_TYPE_PUT_OBJECT,
MetaRequestType::CopyObject => aws_s3_meta_request_type::AWS_S3_META_REQUEST_TYPE_COPY_OBJECT,
}
}
}
unsafe extern "C" fn meta_request_telemetry_callback(
_request: *mut aws_s3_meta_request,
metrics: *mut aws_s3_request_metrics,
user_data: *mut libc::c_void,
) {
let user_data = MetaRequestOptionsInner::from_user_data_ptr(user_data);
if let Some(callback) = user_data.on_telemetry.as_ref() {
let metrics = NonNull::new(metrics as *mut aws_s3_request_metrics).expect("request metrics is never null");
let metrics = RequestMetrics { inner: metrics };
callback(&metrics);
}
}
unsafe extern "C" fn meta_request_headers_callback(
_request: *mut aws_s3_meta_request,
headers: *const aws_http_headers,
response_status: i32,
user_data: *mut libc::c_void,
) -> i32 {
let user_data = MetaRequestOptionsInner::from_user_data_ptr(user_data);
if let Some(callback) = user_data.on_headers.as_mut() {
let headers = NonNull::new(headers as *mut aws_http_headers).expect("request headers is never null");
let headers = Headers::from_crt(headers);
callback(&headers, response_status);
}
AWS_OP_SUCCESS
}
unsafe extern "C" fn meta_request_receive_body_callback(
_request: *mut aws_s3_meta_request,
body: *const aws_byte_cursor,
range_start: u64,
user_data: *mut libc::c_void,
) -> i32 {
let user_data = MetaRequestOptionsInner::from_user_data_ptr(user_data);
if let Some(callback) = user_data.on_body.as_mut() {
let slice: &[u8] = aws_byte_cursor_as_slice(&*body);
callback(range_start, slice);
}
AWS_OP_SUCCESS
}
unsafe extern "C" fn meta_request_finish_callback(
_request: *mut aws_s3_meta_request,
result: *const aws_s3_meta_request_result,
user_data: *mut libc::c_void,
) {
let result = result.as_ref().expect("result cannot be NULL");
let user_data = MetaRequestOptionsInner::from_user_data_ptr(user_data);
if let Some(callback) = user_data.on_finish.take() {
callback(MetaRequestResult::from_crt_result(result));
}
}
unsafe extern "C" fn meta_request_shutdown_callback(user_data: *mut libc::c_void) {
let user_data = MetaRequestOptionsInner::from_user_data_ptr_owned(user_data);
std::mem::drop(user_data);
}
#[derive(Debug)]
pub struct MetaRequest {
#[allow(unused)]
inner: NonNull<aws_s3_meta_request>,
}
impl Drop for MetaRequest {
fn drop(&mut self) {
unsafe {
aws_s3_meta_request_release(self.inner.as_ptr());
}
}
}
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct ClientMetrics {
pub num_requests_tracked_requests: u32,
pub num_requests_being_prepared: u32,
pub request_queue_size: u32,
pub num_auto_default_network_io: u32,
pub num_auto_ranged_get_network_io: u32,
pub num_auto_ranged_put_network_io: u32,
pub num_auto_ranged_copy_network_io: u32,
pub num_requests_stream_queued_waiting: u32,
pub num_requests_streaming: u32,
}
impl ClientMetrics {
pub fn num_total_network_io(&self) -> u32 {
self.num_auto_default_network_io
+ self.num_auto_ranged_get_network_io
+ self.num_auto_ranged_put_network_io
+ self.num_auto_ranged_copy_network_io
}
}
impl Client {
pub fn new(allocator: &Allocator, config: ClientConfig) -> Result<Self, Error> {
s3_library_init(allocator);
let inner = unsafe { aws_s3_client_new(allocator.inner.as_ptr(), &config.inner).ok_or_last_error()? };
Ok(Self { inner, config })
}
pub fn make_meta_request(&self, mut options: MetaRequestOptions) -> Result<MetaRequest, Error> {
unsafe {
if let Some(signing_config) = self.config.signing_config.as_ref() {
options.signing_config(signing_config.clone());
}
let options = Pin::into_inner_unchecked(options.0);
let options = Box::leak(options);
let inner = aws_s3_client_make_meta_request(self.inner.as_ptr(), &options.inner)
.ok_or_last_error()
.on_err(|| std::mem::drop(Box::from_raw(options)))?;
Ok(MetaRequest { inner })
}
}
pub fn poll_client_metrics(&self) -> ClientMetrics {
unsafe {
let client = self.inner.as_ref();
let stats = client.stats;
let num_requests_tracked_requests = aws_atomic_load_int(&stats.num_requests_in_flight) as u32;
let num_auto_ranged_get_network_io = Client::get_num_requests_network_io(
client,
aws_s3_meta_request_type::AWS_S3_META_REQUEST_TYPE_GET_OBJECT,
);
let num_auto_ranged_put_network_io = Client::get_num_requests_network_io(
client,
aws_s3_meta_request_type::AWS_S3_META_REQUEST_TYPE_PUT_OBJECT,
);
let num_auto_default_network_io =
Client::get_num_requests_network_io(client, aws_s3_meta_request_type::AWS_S3_META_REQUEST_TYPE_DEFAULT);
let num_auto_ranged_copy_network_io = Client::get_num_requests_network_io(
client,
aws_s3_meta_request_type::AWS_S3_META_REQUEST_TYPE_COPY_OBJECT,
);
let num_requests_stream_queued_waiting =
aws_atomic_load_int(&stats.num_requests_stream_queued_waiting) as u32;
let num_requests_streaming = aws_atomic_load_int(&stats.num_requests_streaming) as u32;
let num_requests_being_prepared = client.threaded_data.num_requests_being_prepared;
let request_queue_size = client.threaded_data.request_queue_size;
ClientMetrics {
num_requests_tracked_requests,
num_requests_being_prepared,
request_queue_size,
num_auto_default_network_io,
num_auto_ranged_get_network_io,
num_auto_ranged_put_network_io,
num_auto_ranged_copy_network_io,
num_requests_stream_queued_waiting,
num_requests_streaming,
}
}
}
fn get_num_requests_network_io(client: &aws_s3_client, meta_request_type: aws_s3_meta_request_type) -> u32 {
let mut num_requests_network_io: u32 = 0;
if meta_request_type == aws_s3_meta_request_type::AWS_S3_META_REQUEST_TYPE_MAX {
let max_req_type = aws_s3_meta_request_type::AWS_S3_META_REQUEST_TYPE_MAX as usize;
for i in 0..max_req_type {
let n = unsafe { aws_atomic_load_int(&client.stats.num_requests_network_io[i]) } as u32;
num_requests_network_io += n;
}
} else {
let meta_request_type = meta_request_type as usize;
let n = unsafe { aws_atomic_load_int(&client.stats.num_requests_network_io[meta_request_type]) } as u32;
num_requests_network_io = n;
}
num_requests_network_io
}
}
impl Drop for Client {
fn drop(&mut self) {
unsafe {
aws_s3_client_release(self.inner.as_ptr());
}
}
}
#[derive(Debug)]
pub struct MetaRequestResult {
pub response_status: i32,
pub crt_error: Error,
pub error_response_headers: Option<Headers>,
pub error_response_body: Option<OsString>,
}
impl MetaRequestResult {
pub fn is_err(&self) -> bool {
self.crt_error.is_err()
}
unsafe fn from_crt_result(inner: &aws_s3_meta_request_result) -> Self {
let error_response_headers = inner
.error_response_headers
.as_ref()
.map(|headers| Headers::from_crt(NonNull::from(headers)));
let error_response_body: Option<OsString> = inner.error_response_body.as_ref().map(|byte_buf| {
assert!(!byte_buf.buffer.is_null(), "error_response_body.buffer is null");
let slice: &[u8] = std::slice::from_raw_parts(byte_buf.buffer, byte_buf.len);
OsStr::from_bytes(slice).to_owned()
});
Self {
response_status: inner.response_status,
crt_error: inner.error_code.into(),
error_response_headers,
error_response_body,
}
}
}
#[derive(Debug)]
pub struct RequestMetrics {
inner: NonNull<aws_s3_request_metrics>,
}
impl RequestMetrics {
pub fn request_type(&self) -> RequestType {
let mut out: aws_s3_request_type = aws_s3_request_type::AWS_S3_REQUEST_TYPE_MAX;
unsafe { aws_s3_request_metrics_get_request_type(self.inner.as_ptr(), &mut out) };
out.into()
}
pub fn request_id(&self) -> Option<String> {
let mut out: *const aws_string = std::ptr::null();
unsafe {
aws_s3_request_metrics_get_request_id(self.inner.as_ptr(), &mut out)
.ok_or_last_error()
.ok()?
};
assert!(!out.is_null(), "request ID should be available if call succeeded");
unsafe {
let byte_cursor = aws_byte_cursor_from_string(out);
let os_str = OsStr::from_bytes(aws_byte_cursor_as_slice(&byte_cursor));
Some(os_str.to_string_lossy().into_owned())
}
}
pub fn status_code(&self) -> Option<i32> {
let mut out: i32 = 0;
unsafe {
aws_s3_request_metrics_get_response_status_code(self.inner.as_ptr(), &mut out)
.ok_or_last_error()
.ok()?
};
Some(out)
}
pub fn response_headers(&self) -> Option<Headers> {
let mut out: *mut aws_http_headers = std::ptr::null_mut();
unsafe {
aws_s3_request_metrics_get_response_headers(self.inner.as_ptr(), &mut out)
.ok_or_last_error()
.ok()?
};
assert!(!out.is_null(), "headers should be available if call succeeded");
unsafe { Some(Headers::from_crt(NonNull::new_unchecked(out))) }
}
pub fn total_duration(&self) -> Duration {
let mut out: u64 = 0;
unsafe { aws_s3_request_metrics_get_total_duration_ns(self.inner.as_ptr(), &mut out) };
Duration::from_nanos(out)
}
pub fn time_to_first_byte(&self) -> Option<Duration> {
let mut send_end: u64 = 0;
let mut receive_start: u64 = 0;
unsafe {
aws_s3_request_metrics_get_send_end_timestamp_ns(self.inner.as_ptr(), &mut send_end)
.ok_or_last_error()
.ok()?;
aws_s3_request_metrics_get_receive_start_timestamp_ns(self.inner.as_ptr(), &mut receive_start)
.ok_or_last_error()
.ok()?;
};
Some(Duration::from_nanos(receive_start.saturating_sub(send_end)))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RequestType {
Default,
HeadObject,
GetObject,
ListParts,
CreateMultipartUpload,
UploadPart,
AbortMultipartUpload,
CompleteMultipartUpload,
UploadPartCopy,
}
impl From<aws_s3_request_type> for RequestType {
fn from(value: aws_s3_request_type) -> Self {
use aws_s3_request_type::*;
match value {
AWS_S3_REQUEST_TYPE_DEFAULT => RequestType::Default,
AWS_S3_REQUEST_TYPE_HEAD_OBJECT => RequestType::HeadObject,
AWS_S3_REQUEST_TYPE_GET_OBJECT => RequestType::GetObject,
AWS_S3_REQUEST_TYPE_LIST_PARTS => RequestType::ListParts,
AWS_S3_REQUEST_TYPE_CREATE_MULTIPART_UPLOAD => RequestType::CreateMultipartUpload,
AWS_S3_REQUEST_TYPE_UPLOAD_PART => RequestType::UploadPart,
AWS_S3_REQUEST_TYPE_ABORT_MULTIPART_UPLOAD => RequestType::AbortMultipartUpload,
AWS_S3_REQUEST_TYPE_COMPLETE_MULTIPART_UPLOAD => RequestType::CompleteMultipartUpload,
AWS_S3_REQUEST_TYPE_UPLOAD_PART_COPY => RequestType::UploadPartCopy,
_ => panic!("unknown request type {:?}", value),
}
}
}
pub fn init_default_signing_config(region: &str, credentials_provider: CredentialsProvider) -> SigningConfig {
let mut signing_config = Box::new(SigningConfigInner {
inner: Default::default(),
region: region.to_owned().into(),
credentials_provider,
_pinned: Default::default(),
});
let credentials_provider = signing_config.credentials_provider.inner.as_ptr();
unsafe {
let region_cursor = signing_config.region.as_aws_byte_cursor();
aws_s3_init_default_signing_config(&mut signing_config.inner, region_cursor, credentials_provider);
}
signing_config.inner.flags.set_use_double_uri_encode(false as u32);
SigningConfig(Arc::new(Box::into_pin(signing_config)))
}