#![doc = include_str!("../README.md")]
#![no_std]
extern crate alloc;
use alloc::string::{String, ToString};
use rasn::prelude::*;
pub type MessageId = u32;
#[derive(AsnType, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(tag(universal, 4))]
pub struct LdapString(pub String);
impl core::ops::Deref for LdapString {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl core::ops::DerefMut for LdapString {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<String> for LdapString {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for LdapString {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[allow(clippy::mutable_key_type)]
impl rasn::Encode for LdapString {
fn encode_with_tag_and_constraints<'encoder, EN: rasn::Encoder<'encoder>>(
&self,
encoder: &mut EN,
tag: rasn::types::Tag,
constraints: rasn::types::Constraints,
identifier: Identifier,
) -> core::result::Result<(), EN::Error> {
encoder.encode_octet_string(tag, constraints, self.0.as_bytes(), identifier)?;
Ok(())
}
}
impl rasn::Decode for LdapString {
fn decode_with_tag_and_constraints<D: rasn::Decoder>(
decoder: &mut D,
tag: rasn::types::Tag,
constraints: rasn::types::Constraints,
) -> core::result::Result<Self, D::Error> {
String::from_utf8(decoder.decode_octet_string(tag, constraints)?)
.map_err(|error| {
rasn::de::Error::custom(
alloc::format!("LdapString not valid UTF-8: {error}"),
decoder.codec(),
)
})
.map(Self::from)
}
}
pub type LdapOid = OctetString;
pub type LdapDn = LdapString;
pub type RelativeLdapDn = LdapString;
pub type AttributeDescription = LdapString;
pub type AttributeValue = OctetString;
pub type AssertionValue = OctetString;
pub type MatchingRuleId = LdapString;
pub type Referral = SequenceOf<Uri>;
pub type Uri = LdapString;
pub type Controls = SequenceOf<Control>;
pub type AttributeSelection = SequenceOf<LdapString>;
pub type PartialAttributeList = SequenceOf<PartialAttribute>;
pub type AttributeList = SequenceOf<Attribute>;
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct LdapMessage {
pub message_id: MessageId,
pub protocol_op: ProtocolOp,
#[rasn(tag(0))]
pub controls: Option<Controls>,
}
impl LdapMessage {
pub fn new(message_id: MessageId, protocol_op: ProtocolOp) -> Self {
LdapMessage {
message_id,
protocol_op,
controls: None,
}
}
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, Eq, Hash)]
#[rasn(choice)]
#[non_exhaustive]
pub enum ProtocolOp {
BindRequest(BindRequest),
BindResponse(BindResponse),
UnbindRequest(UnbindRequest),
SearchRequest(SearchRequest),
SearchResEntry(SearchResultEntry),
SearchResDone(SearchResultDone),
SearchResRef(SearchResultReference),
ModifyRequest(ModifyRequest),
ModifyResponse(ModifyResponse),
AddRequest(AddRequest),
AddResponse(AddResponse),
DelRequest(DelRequest),
DelResponse(DelResponse),
ModDnRequest(ModifyDnRequest),
ModDnResponse(ModifyDnResponse),
CompareRequest(CompareRequest),
CompareResponse(CompareResponse),
AbandonRequest(AbandonRequest),
ExtendedReq(ExtendedRequest),
ExtendedResp(ExtendedResponse),
IntermediateResponse(IntermediateResponse),
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[non_exhaustive]
pub struct AttributeValueAssertion {
pub attribute_desc: AttributeDescription,
pub assertion_value: AssertionValue,
}
impl AttributeValueAssertion {
pub fn new(attribute_desc: AttributeDescription, assertion_value: AssertionValue) -> Self {
Self {
attribute_desc,
assertion_value,
}
}
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct PartialAttribute {
pub r#type: AttributeDescription,
pub vals: SetOf<AttributeValue>,
}
impl PartialAttribute {
pub fn new(r#type: AttributeDescription, vals: SetOf<AttributeValue>) -> Self {
Self { r#type, vals }
}
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct Attribute {
pub r#type: AttributeDescription,
pub vals: SetOf<AttributeValue>,
}
impl Attribute {
pub fn new(r#type: AttributeDescription, vals: SetOf<AttributeValue>) -> Self {
Self { r#type, vals }
}
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[non_exhaustive]
pub struct LdapResult {
pub result_code: ResultCode,
pub matched_dn: LdapDn,
pub diagnostic_message: LdapString,
#[rasn(tag(3))]
pub referral: Option<Referral>,
}
impl LdapResult {
pub fn new(
result_code: ResultCode,
matched_dn: LdapDn,
diagnostic_message: LdapString,
) -> Self {
Self {
result_code,
matched_dn,
diagnostic_message,
referral: None,
}
}
}
#[derive(AsnType, Encode, Decode, Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(enumerated)]
#[non_exhaustive]
pub enum ResultCode {
Success = 0,
OperationsError = 1,
ProtocolError = 2,
TimeLimitExceeded = 3,
SizeLimitExceeded = 4,
CompareFalse = 5,
CompareTrue = 6,
AuthMethodNotSupported = 7,
StrongerAuthRequired = 8,
Referral = 10,
AdminLimitExceeded = 11,
UnavailableCriticalExtension = 12,
ConfidentialityRequired = 13,
SaslBindInProgress = 14,
NoSuchAttribute = 16,
UndefinedAttributeType = 17,
InappropriateMatching = 18,
ConstraintViolation = 19,
AttributeOrValueExists = 20,
InvalidAttributeSyntax = 21,
NoSuchObject = 32,
AliasProblem = 33,
InvalidDnSyntax = 34,
AliasDereferencingProblem = 36,
InappropriateAuthentication = 48,
InvalidCredentials = 49,
InsufficientAccessRights = 50,
Busy = 51,
Unavailable = 52,
UnwillingToPerform = 53,
LoopDetect = 54,
NamingViolation = 64,
ObjectClassViolation = 65,
NotAllowedOnNonLeaf = 66,
NotAllowedOnRdn = 67,
EntryAlreadyExists = 68,
ObjectClassModsProhibited = 69,
AffectsMultipleDsas = 71,
Other = 80,
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[non_exhaustive]
pub struct Control {
pub control_type: LdapOid,
#[rasn(default)]
pub criticality: bool,
pub control_value: Option<OctetString>,
}
impl Control {
pub fn new(
control_type: LdapOid,
criticality: bool,
control_value: Option<OctetString>,
) -> Self {
Self {
control_type,
criticality,
control_value,
}
}
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[non_exhaustive]
#[rasn(tag(application, 0))]
pub struct BindRequest {
pub version: u8,
pub name: LdapDn,
pub authentication: AuthenticationChoice,
}
impl BindRequest {
pub fn new(version: u8, name: LdapDn, authentication: AuthenticationChoice) -> Self {
Self {
version,
name,
authentication,
}
}
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(choice)]
#[non_exhaustive]
pub enum AuthenticationChoice {
#[rasn(tag(0))]
Simple(OctetString),
#[rasn(tag(3))]
Sasl(SaslCredentials),
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[non_exhaustive]
pub struct SaslCredentials {
pub mechanism: LdapString,
pub credentials: Option<OctetString>,
}
impl SaslCredentials {
pub fn new(mechanism: LdapString, credentials: Option<OctetString>) -> Self {
Self {
mechanism,
credentials,
}
}
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[non_exhaustive]
#[rasn(tag(application, 1))]
pub struct BindResponse {
pub result_code: ResultCode,
pub matched_dn: LdapDn,
pub diagnostic_message: LdapString,
#[rasn(tag(3))]
pub referral: Option<Referral>,
#[rasn(tag(7))]
pub server_sasl_creds: Option<OctetString>,
}
impl BindResponse {
pub fn new(
result_code: ResultCode,
matched_dn: LdapDn,
diagnostic_message: LdapString,
referral: Option<Referral>,
server_sasl_creds: Option<OctetString>,
) -> Self {
Self {
result_code,
matched_dn,
diagnostic_message,
referral,
server_sasl_creds,
}
}
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(tag(application, 2))]
pub struct UnbindRequest;
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, Eq, Hash)]
#[rasn(tag(application, 3))]
#[non_exhaustive]
pub struct SearchRequest {
pub base_object: LdapDn,
pub scope: SearchRequestScope,
pub deref_aliases: SearchRequestDerefAliases,
pub size_limit: u32,
pub time_limit: u32,
pub types_only: bool,
pub filter: Filter,
pub attributes: AttributeSelection,
}
impl SearchRequest {
#[allow(clippy::too_many_arguments)]
pub fn new(
base_object: LdapDn,
scope: SearchRequestScope,
deref_aliases: SearchRequestDerefAliases,
size_limit: u32,
time_limit: u32,
types_only: bool,
filter: Filter,
attributes: AttributeSelection,
) -> Self {
Self {
base_object,
scope,
deref_aliases,
size_limit,
time_limit,
types_only,
filter,
attributes,
}
}
}
#[derive(AsnType, Encode, Decode, Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(enumerated)]
#[non_exhaustive]
pub enum SearchRequestScope {
BaseObject = 0,
SingleLevel = 1,
WholeSubtree = 2,
}
#[derive(AsnType, Encode, Decode, Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(enumerated)]
#[non_exhaustive]
pub enum SearchRequestDerefAliases {
NeverDerefAliases = 0,
DerefInSearching = 1,
DerefFindingBaseObj = 2,
DerefAlways = 3,
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, Eq, Hash)]
#[rasn(choice)]
#[non_exhaustive]
pub enum Filter {
#[rasn(tag(0))]
And(SetOf<Filter>),
#[rasn(tag(1))]
Or(SetOf<Filter>),
#[rasn(tag(2))]
Not(alloc::boxed::Box<Filter>),
#[rasn(tag(3))]
EqualityMatch(AttributeValueAssertion),
#[rasn(tag(4))]
Substrings(SubstringFilter),
#[rasn(tag(5))]
GreaterOrEqual(AttributeValueAssertion),
#[rasn(tag(6))]
LessOrEqual(AttributeValueAssertion),
#[rasn(tag(7))]
Present(AttributeDescription),
#[rasn(tag(8))]
ApproxMatch(AttributeValueAssertion),
#[rasn(tag(9))]
ExtensibleMatch(MatchingRuleAssertion),
}
impl core::ops::Not for Filter {
type Output = Self;
fn not(self) -> Self {
Self::Not(alloc::boxed::Box::new(self))
}
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[non_exhaustive]
pub struct SubstringFilter {
pub r#type: AttributeDescription,
pub substrings: SequenceOf<SubstringChoice>,
}
impl SubstringFilter {
pub fn new(r#type: AttributeDescription, substrings: SequenceOf<SubstringChoice>) -> Self {
Self { r#type, substrings }
}
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(choice)]
#[non_exhaustive]
pub enum SubstringChoice {
#[rasn(tag(0))]
Initial(AssertionValue),
#[rasn(tag(1))]
Any(AssertionValue),
#[rasn(tag(2))]
Final(AssertionValue),
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[non_exhaustive]
pub struct MatchingRuleAssertion {
#[rasn(tag(1))]
pub matching_rule: Option<MatchingRuleId>,
#[rasn(tag(2))]
pub r#type: Option<AttributeDescription>,
#[rasn(tag(3))]
pub match_value: AssertionValue,
#[rasn(tag(4), default)]
pub dn_attributes: bool,
}
impl MatchingRuleAssertion {
pub fn new(
matching_rule: Option<MatchingRuleId>,
r#type: Option<AttributeDescription>,
match_value: AssertionValue,
dn_attributes: bool,
) -> Self {
Self {
matching_rule,
r#type,
match_value,
dn_attributes,
}
}
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, Eq, Hash)]
#[rasn(tag(application, 4))]
#[non_exhaustive]
pub struct SearchResultEntry {
pub object_name: LdapDn,
pub attributes: PartialAttributeList,
}
impl SearchResultEntry {
pub fn new(object_name: LdapDn, attributes: PartialAttributeList) -> Self {
Self {
object_name,
attributes,
}
}
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(tag(application, 19))]
#[rasn(delegate)]
pub struct SearchResultReference(pub SequenceOf<Uri>);
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(tag(application, 5))]
#[rasn(delegate)]
pub struct SearchResultDone(pub LdapResult);
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, Eq, Hash)]
#[rasn(tag(application, 6))]
pub struct ModifyRequest {
pub object: LdapDn,
pub changes: SequenceOf<ModifyRequestChanges>,
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, Eq, Hash)]
pub struct ModifyRequestChanges {
pub operation: ChangeOperation,
pub modification: PartialAttribute,
}
#[derive(AsnType, Encode, Decode, Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(enumerated)]
pub enum ChangeOperation {
Add = 0,
Delete = 1,
Replace = 2,
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(delegate, tag(application, 7))]
pub struct ModifyResponse(pub LdapResult);
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, Eq, Hash)]
#[rasn(tag(application, 8))]
pub struct AddRequest {
pub entry: LdapDn,
pub attributes: AttributeList,
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(delegate, tag(application, 9))]
pub struct AddResponse(pub LdapResult);
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(delegate, tag(application, 10))]
pub struct DelRequest(pub LdapDn);
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(delegate, tag(application, 11))]
pub struct DelResponse(pub LdapResult);
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(tag(application, 12))]
pub struct ModifyDnRequest {
pub entry: LdapDn,
pub new_rdn: RelativeLdapDn,
pub delete_old_rdn: bool,
#[rasn(tag(0))]
pub new_superior: Option<LdapDn>,
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(delegate, tag(application, 13))]
pub struct ModifyDnResponse(pub LdapResult);
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(tag(application, 14))]
pub struct CompareRequest {
pub entry: LdapDn,
pub ava: AttributeValueAssertion,
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(delegate, tag(application, 15))]
pub struct CompareResponse(pub LdapResult);
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(delegate, tag(application, 16))]
pub struct AbandonRequest(pub MessageId);
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(tag(application, 23))]
pub struct ExtendedRequest {
#[rasn(tag(0))]
pub request_name: LdapOid,
#[rasn(tag(1))]
pub request_value: Option<OctetString>,
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(tag(application, 24))]
pub struct ExtendedResponse {
pub result_code: ResultCode,
pub matched_dn: LdapDn,
pub diagnostic_message: LdapString,
#[rasn(tag(3))]
pub referral: Option<Referral>,
#[rasn(tag(10))]
pub response_name: Option<LdapOid>,
#[rasn(tag(11))]
pub response_value: Option<OctetString>,
}
#[derive(AsnType, Encode, Decode, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[rasn(tag(application, 25))]
pub struct IntermediateResponse {
#[rasn(tag(0))]
pub response_name: Option<LdapOid>,
#[rasn(tag(1))]
pub response_value: Option<OctetString>,
}
mod tests {
#[test]
fn test_ldpa_string() {
use super::{LdapString, ToString};
let ldap_string = LdapString("test".to_string());
let ldap_string2 = LdapString("test".into());
assert_eq!(ldap_string, ldap_string2);
let encoded = rasn::ber::encode(&ldap_string).unwrap();
assert_eq!(encoded, alloc::vec![0x04, 0x04, 0x74, 0x65, 0x73, 0x74]);
let decoded: LdapString = rasn::ber::decode(&encoded).unwrap();
assert_eq!(ldap_string, decoded);
let invalid_utf8: &[u8] = &[0x04, 0x04, 0x80, 0xC0, 0xF5, 0xFF];
let decoded = rasn::ber::decode::<LdapString>(invalid_utf8);
assert!(decoded.is_err());
}
}