use bytes::BytesMut;
use derive_more::TryFrom;
use itertools::Itertools;
use ldap3::{
asn1::{
ASNTag, Boolean, OctetString, Sequence, Tag, TagClass, Types, parse_tag, parse_uint, write,
},
controls::{ControlParser, MakeCritical, RawControl},
};
use crate::sort::{SERVER_SIDE_SORT_REQUEST_OID, adapter::SortBy};
pub(crate) struct ServerSideSortRequest {
pub sort_key_list: Vec<SortKey>,
}
impl MakeCritical for ServerSideSortRequest {}
impl From<ServerSideSortRequest> for RawControl {
fn from(value: ServerSideSortRequest) -> Self {
let tagged = Tag::Sequence(Sequence {
inner: value.sort_key_list.into_iter().map_into().collect(),
..Default::default()
})
.into_structure();
let mut buffer = BytesMut::new();
write::encode_into(&mut buffer, tagged).expect("Encoding should pass");
RawControl {
ctype: SERVER_SIDE_SORT_REQUEST_OID.to_owned(),
crit: false,
val: Some(buffer.into()),
}
}
}
#[derive(Debug)]
pub(crate) struct SortKey {
pub attribute_type: String,
pub ordering_rule: Option<String>,
pub reverse_order: bool,
}
impl From<SortBy> for SortKey {
fn from(value: SortBy) -> Self {
SortKey {
attribute_type: value.attribute,
ordering_rule: None,
reverse_order: value.reverse,
}
}
}
const ORDERING_RULE_TAG: u64 = 0;
const REVERSE_ORDER_TAG: u64 = 1;
impl From<SortKey> for Tag {
fn from(value: SortKey) -> Self {
let iterator = [
Some(Tag::OctetString(OctetString {
inner: value.attribute_type.into(),
..Default::default()
})),
value.ordering_rule.map(|rule| {
Tag::OctetString(OctetString {
id: ORDERING_RULE_TAG,
class: TagClass::Context,
inner: rule.into(),
})
}),
Some(Tag::Boolean(Boolean {
id: REVERSE_ORDER_TAG,
class: TagClass::Context,
inner: value.reverse_order,
})),
]
.into_iter()
.flatten();
Tag::Sequence(Sequence {
inner: iterator.collect(),
..Sequence::default()
})
}
}
#[derive(Debug)]
pub(crate) struct ServerSideSortResponse {
pub sort_result: SortResult,
#[expect(
dead_code,
reason = "It's here per the spec. May have some uses in error cases."
)]
pub attribute_type: Option<String>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFrom)]
#[try_from(repr)]
#[repr(u64)]
pub(crate) enum SortResult {
Success = 0,
OperationsError = 1,
TimeLimitExceeded = 3,
StrongAuthRequired = 8,
AdminLimitExceeded = 11,
NoSuchAttribute = 16,
InappropriateMatching = 18,
InsufficientAccessRights = 50,
Busy = 51,
UnwillingToPerform = 53,
Other = 80,
}
const ATTRIBUTE_TYPE_TAG: u64 = 0;
impl ControlParser for ServerSideSortResponse {
fn parse(val: &[u8]) -> Self {
let mut sequence_components = match parse_tag(val) {
Ok((_, tag)) => tag,
_ => panic!("failed to parse server side sort response control components"),
}
.expect_constructed()
.expect("server side sort results components")
.into_iter();
let raw_sort_result = sequence_components
.next()
.expect("server side sort element 1")
.match_class(TagClass::Universal)
.and_then(|tag| tag.match_id(Types::Enumerated as u64))
.and_then(|tag| tag.expect_primitive())
.expect("sortResult");
let (_, numeric_sort_result) =
parse_uint(raw_sort_result.as_slice()).expect("should have been a sort result");
let sort_result = SortResult::try_from(numeric_sort_result)
.expect("should have been a valid sort result code");
if sort_result == SortResult::Success {
ServerSideSortResponse {
sort_result,
attribute_type: None,
}
} else {
let attribute_type = sequence_components
.next()
.and_then(|tag| tag.match_class(TagClass::Context))
.and_then(|tag| tag.match_id(ATTRIBUTE_TYPE_TAG))
.and_then(|tag| tag.expect_primitive())
.map(String::from_utf8)
.transpose()
.expect("should be an AttributeType Description");
ServerSideSortResponse {
sort_result,
attribute_type,
}
}
}
}