use std::ffi::CString;
use std::marker::PhantomData;
use std::rc::Rc;
use std::time::{Duration, Instant};
use crate::correlation::CorrelationId;
use crate::errors::{BlpError, Result};
use crate::event::Event;
use crate::identity::Identity;
use crate::message::Message;
use crate::request::Request;
use crate::service::Service;
use crate::subscription::SubscriptionList;
pub use crate::options::SessionOptions;
pub struct Session {
ptr: *mut crate::ffi::blpapi_Session_t,
_not_send_sync: PhantomData<Rc<()>>,
}
impl Session {
const STARTUP_POLL_TIMEOUT_MS: u32 = 250;
pub fn new(options: &SessionOptions) -> Result<Self> {
let ptr = unsafe {
crate::ffi::blpapi_Session_create(
options.as_raw(),
None, std::ptr::null_mut(), std::ptr::null_mut(), )
};
if ptr.is_null() {
return Err(BlpError::SessionStart {
source: None,
label: None,
});
}
Ok(Self {
ptr,
_not_send_sync: PhantomData,
})
}
pub fn start(&self) -> Result<()> {
let rc = unsafe { crate::ffi::blpapi_Session_start(self.ptr) };
if rc != 0 {
return Err(BlpError::SessionStart {
source: None,
label: None,
});
}
Ok(())
}
pub fn wait_until_started(&self, timeout_ms: u32) -> Result<()> {
let deadline = Instant::now() + Duration::from_millis(u64::from(timeout_ms));
loop {
let now = Instant::now();
if now >= deadline {
return Err(BlpError::Timeout);
}
let remaining = deadline.saturating_duration_since(now);
let poll_timeout = remaining
.min(Duration::from_millis(u64::from(
Self::STARTUP_POLL_TIMEOUT_MS,
)))
.as_millis() as u32;
let poll_timeout = poll_timeout.max(1);
let event = match self.next_event(Some(poll_timeout)) {
Ok(event) => event,
Err(BlpError::Timeout) => continue,
Err(err) => return Err(err),
};
let mut saw_session_started = false;
for msg in event.messages() {
match msg.message_type().as_str() {
"SessionStarted" => saw_session_started = true,
"SessionStartupFailure" => {
return Err(startup_error_from_message("session startup failure", &msg));
}
"SessionTerminated" => {
return Err(startup_error_from_message(
"session terminated during startup",
&msg,
));
}
"AuthorizationFailure" => {
return Err(startup_error_from_message(
"session identity authorization failed",
&msg,
));
}
"AuthorizationRevoked" => {
return Err(startup_error_from_message(
"session identity authorization revoked",
&msg,
));
}
_ => {}
}
}
if saw_session_started {
return Ok(());
}
}
}
pub fn start_and_wait(&self, timeout_ms: u32) -> Result<()> {
self.start()?;
self.wait_until_started(timeout_ms)
}
pub fn stop(&self) {
unsafe {
crate::ffi::blpapi_Session_stop(self.ptr);
}
}
pub fn next_event(&self, timeout_ms: Option<u32>) -> Result<Event> {
let mut event_ptr: *mut crate::ffi::blpapi_Event_t = std::ptr::null_mut();
let rc = unsafe {
crate::ffi::blpapi_Session_nextEvent(self.ptr, &mut event_ptr, timeout_ms.unwrap_or(0))
};
if rc != 0 {
return Err(BlpError::Timeout);
}
if event_ptr.is_null() {
return Err(BlpError::Internal {
detail: "nextEvent returned null event".into(),
});
}
Ok(unsafe { Event::from_raw(event_ptr) })
}
pub fn try_next_event(&self) -> Option<Event> {
let mut event_ptr: *mut crate::ffi::blpapi_Event_t = std::ptr::null_mut();
let rc = unsafe { crate::ffi::blpapi_Session_tryNextEvent(self.ptr, &mut event_ptr) };
if rc == 0 && !event_ptr.is_null() {
Some(unsafe { Event::from_raw(event_ptr) })
} else {
None
}
}
pub fn open_service(&self, name: &str) -> Result<()> {
let c_name = CString::new(name).map_err(|e| BlpError::InvalidArgument {
detail: format!("invalid service name: {}", e),
})?;
let rc = unsafe { crate::ffi::blpapi_Session_openService(self.ptr, c_name.as_ptr()) };
if rc != 0 {
return Err(BlpError::OpenService {
service: name.to_string(),
source: None,
label: None,
});
}
Ok(())
}
pub fn open_service_async(&self, name: &str, cid: &CorrelationId) -> Result<CorrelationId> {
let c_name = CString::new(name).map_err(|e| BlpError::InvalidArgument {
detail: format!("invalid service name: {}", e),
})?;
let mut cid_ffi = cid.to_ffi();
let rc = unsafe {
crate::ffi::blpapi_Session_openServiceAsync(self.ptr, c_name.as_ptr(), &mut cid_ffi)
};
if rc != 0 {
return Err(BlpError::OpenService {
service: name.to_string(),
source: None,
label: None,
});
}
Ok(CorrelationId::from_ffi(&cid_ffi))
}
pub fn get_service(&self, name: &str) -> Result<Service<'_>> {
let c_name = CString::new(name).map_err(|e| BlpError::InvalidArgument {
detail: format!("invalid service name: {}", e),
})?;
let mut service_ptr: *mut crate::ffi::blpapi_Service_t = std::ptr::null_mut();
let rc = unsafe {
crate::ffi::blpapi_Session_getService(self.ptr, &mut service_ptr, c_name.as_ptr())
};
if rc != 0 {
return Err(BlpError::OpenService {
service: name.to_string(),
source: None,
label: None,
});
}
Service::from_raw(service_ptr)
}
pub fn send_request(
&self,
req: &Request,
identity: Option<&Identity>,
cid: Option<&CorrelationId>,
) -> Result<CorrelationId> {
self.send_request_with_label(req, identity, cid, None)
}
pub fn send_request_with_label(
&self,
req: &Request,
identity: Option<&Identity>,
cid: Option<&CorrelationId>,
label: Option<&str>,
) -> Result<CorrelationId> {
let mut cid_ffi = match cid {
Some(c) => c.to_ffi(),
None => CorrelationId::default().to_ffi(),
};
let identity_ptr = match identity {
Some(id) => id.as_ptr(),
None => std::ptr::null_mut(),
};
let (label_ptr, label_len, _label_cstring) = match label {
Some(value) => {
let cstring = CString::new(value).map_err(|e| BlpError::InvalidArgument {
detail: format!("invalid request label: {e}"),
})?;
(cstring.as_ptr(), value.len() as i32, Some(cstring))
}
None => (std::ptr::null(), 0, None),
};
let rc = unsafe {
crate::ffi::blpapi_Session_sendRequest(
self.ptr,
req.as_ptr(),
&mut cid_ffi,
identity_ptr,
std::ptr::null_mut(), label_ptr,
label_len,
)
};
if rc != 0 {
return Err(BlpError::Internal {
detail: format!("blpapi_Session_sendRequest failed with rc={}", rc),
});
}
Ok(CorrelationId::from_ffi(&cid_ffi))
}
pub fn subscribe(&self, subs: &SubscriptionList, label: Option<&str>) -> Result<()> {
let (label_ptr, label_len, _label_cstring) = match label {
Some(l) => {
let cs = CString::new(l).map_err(|e| BlpError::InvalidArgument {
detail: format!("invalid label: {}", e),
})?;
let len = l.len() as i32;
(cs.as_ptr(), len, Some(cs))
}
None => (std::ptr::null(), 0, None),
};
let rc = unsafe {
crate::ffi::blpapi_Session_subscribe(
self.ptr,
subs.as_ptr(),
std::ptr::null(), label_ptr,
label_len,
)
};
if rc != 0 {
return Err(BlpError::Internal {
detail: format!("blpapi_Session_subscribe failed with rc={}", rc),
});
}
Ok(())
}
pub fn unsubscribe(&self, subs: &SubscriptionList) -> Result<()> {
let rc = unsafe {
crate::ffi::blpapi_Session_unsubscribe(
self.ptr,
subs.as_ptr(),
std::ptr::null(), 0, )
};
if rc != 0 {
return Err(BlpError::Internal {
detail: format!("blpapi_Session_unsubscribe failed with rc={}", rc),
});
}
Ok(())
}
pub fn cancel(&self, cid: &CorrelationId) -> Result<()> {
let cid_ffi = cid.to_ffi();
let rc = unsafe {
crate::ffi::blpapi_Session_cancel(self.ptr, &cid_ffi, 1, std::ptr::null(), 0)
};
if rc != 0 {
return Err(BlpError::Internal {
detail: format!("blpapi_Session_cancel failed with rc={rc}"),
});
}
Ok(())
}
pub fn create_identity(&self) -> Result<Identity> {
let identity_ptr = unsafe { crate::ffi::blpapi_Session_createIdentity(self.ptr) };
Identity::from_raw(identity_ptr)
}
pub fn generate_token(&self, cid: Option<&CorrelationId>) -> Result<CorrelationId> {
let mut cid_ffi = match cid {
Some(c) => c.to_ffi(),
None => CorrelationId::default().to_ffi(),
};
let rc = unsafe {
crate::ffi::blpapi_Session_generateToken(
self.ptr,
&mut cid_ffi,
std::ptr::null_mut(), )
};
if rc != 0 {
return Err(BlpError::Internal {
detail: format!("blpapi_Session_generateToken failed with rc={rc}"),
});
}
Ok(CorrelationId::from_ffi(&cid_ffi))
}
pub fn send_authorization_request(
&self,
request: &Request,
identity: &mut Identity,
cid: Option<&CorrelationId>,
) -> Result<CorrelationId> {
let mut cid_ffi = match cid {
Some(c) => c.to_ffi(),
None => CorrelationId::default().to_ffi(),
};
let rc = unsafe {
crate::ffi::blpapi_Session_sendAuthorizationRequest(
self.ptr,
request.as_ptr(),
identity.as_ptr(),
&mut cid_ffi,
std::ptr::null_mut(), std::ptr::null(), 0, )
};
if rc != 0 {
return Err(BlpError::Internal {
detail: format!("blpapi_Session_sendAuthorizationRequest failed with rc={rc}"),
});
}
Ok(CorrelationId::from_ffi(&cid_ffi))
}
pub fn subscribe_with_identity(
&self,
subs: &SubscriptionList,
identity: &Identity,
label: Option<&str>,
) -> Result<()> {
let (label_ptr, label_len, _label_cstring) = match label {
Some(l) => {
let cs = CString::new(l).map_err(|e| BlpError::InvalidArgument {
detail: format!("invalid label: {e}"),
})?;
let len = l.len() as i32;
(cs.as_ptr(), len, Some(cs))
}
None => (std::ptr::null(), 0, None),
};
let rc = unsafe {
crate::ffi::blpapi_Session_subscribe(
self.ptr,
subs.as_ptr(),
identity.as_ptr(),
label_ptr,
label_len,
)
};
if rc != 0 {
return Err(BlpError::Internal {
detail: format!("blpapi_Session_subscribe (with identity) failed with rc={rc}"),
});
}
Ok(())
}
}
fn startup_error_from_message(default_label: &str, msg: &Message<'_>) -> BlpError {
let label = extract_reason_description(msg).unwrap_or_else(|| default_label.to_string());
BlpError::SessionStart {
source: None,
label: Some(label),
}
}
fn extract_reason_description(msg: &Message<'_>) -> Option<String> {
let reason = msg.elements().get_by_str("reason")?;
if let Some(description) = reason
.get_by_str("description")
.and_then(|value| value.get_str(0))
{
return Some(description.to_string());
}
if let Some(category) = reason
.get_by_str("category")
.and_then(|value| value.get_str(0))
{
return Some(category.to_string());
}
if let Some(message) = reason
.get_by_str("message")
.and_then(|value| value.get_str(0))
{
return Some(message.to_string());
}
None
}
impl Drop for Session {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe {
crate::ffi::blpapi_Session_stop(self.ptr);
crate::ffi::blpapi_Session_destroy(self.ptr);
}
self.ptr = std::ptr::null_mut();
}
}
}