Coverage Report

Created: 2025-11-16 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/ldap-parser-0.5.0/src/parser.rs
Line
Count
Source
1
// DEFINITIONS
2
// IMPLICIT TAGS
3
// EXTENSIBILITY IMPLIED
4
5
use crate::error::*;
6
use crate::filter::*;
7
use crate::ldap::*;
8
use asn1_rs::nom;
9
use asn1_rs::{
10
    Class, Enumerated, FromBer, Header, Implicit, OptTaggedParser, ParseResult, Sequence, Tag,
11
    TaggedParser, TaggedValue,
12
};
13
use nom::bytes::streaming::take;
14
use nom::combinator::{complete, map, opt, verify};
15
use nom::multi::{many0, many1};
16
use nom::Err;
17
use std::borrow::Cow;
18
19
// // maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) --
20
// const MAX_INT: u32 = 2_147_483_647;
21
22
// MessageID ::= INTEGER (0 ..  maxInt)
23
impl<'a> FromBer<'a, LdapError> for MessageID {
24
707k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
25
707k
        map(u32::from_ber, MessageID)(bytes).map_err(Err::convert)
26
707k
    }
27
}
28
29
// LDAPString ::= OCTET STRING -- UTF-8 encoded,
30
//                             -- [ISO10646] characters
31
impl<'a> FromBer<'a, LdapError> for LdapString<'a> {
32
70.9k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
33
70.9k
        let (i, b) = parse_ldap_octet_string_as_slice(bytes)?;
34
        // convert to UTF-8
35
48.5k
        let s = std::str::from_utf8(b).or(Err(Err::Error(LdapError::InvalidString)))?;
36
48.3k
        Ok((i, LdapString(Cow::Borrowed(s))))
37
70.9k
    }
38
}
39
40
#[inline]
41
95.0k
pub(crate) fn parse_ldap_octet_string_as_slice(i: &[u8]) -> Result<&[u8]> {
42
95.0k
    <&[u8]>::from_ber(i).map_err(Err::convert)
43
95.0k
}
44
45
#[inline]
46
25.4k
fn parse_ldap_int_as_u32(i: &[u8]) -> Result<u32> {
47
25.4k
    <u32>::from_ber(i).map_err(Err::convert)
48
25.4k
}
49
50
#[inline]
51
44.7k
fn parse_ldap_enum_as_u32(i: &[u8]) -> Result<u32> {
52
44.7k
    let (i, obj) = Enumerated::from_ber(i).map_err(Err::convert)?;
53
40.7k
    Ok((i, obj.0))
54
44.7k
}
55
56
// LDAPDN ::= LDAPString -- Constrained to <distinguishedName>
57
//                       -- [RFC4514]
58
impl<'a> FromBer<'a, LdapError> for LdapDN<'a> {
59
58.0k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
60
        // read bytes
61
58.0k
        let (i, b) = <&[u8]>::from_ber(bytes).map_err(Err::convert)?;
62
        // convert to UTF-8
63
48.1k
        let s = std::str::from_utf8(b).or(Err(Err::Error(LdapError::InvalidDN)))?;
64
48.0k
        Ok((i, LdapDN(Cow::Borrowed(s))))
65
58.0k
    }
66
}
67
68
// RelativeLDAPDN ::= LDAPString -- Constrained to <name-component>
69
//                               -- [RFC4514]
70
impl<'a> FromBer<'a, LdapError> for RelativeLdapDN<'a> {
71
1.84k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
72
        // read bytes
73
1.84k
        let (i, b) = <&[u8]>::from_ber(bytes).map_err(Err::convert)?;
74
        // convert to UTF-8
75
1.57k
        let s = std::str::from_utf8(b).or(Err(Err::Error(LdapError::InvalidDN)))?;
76
1.57k
        Ok((i, RelativeLdapDN(Cow::Borrowed(s))))
77
1.84k
    }
78
}
79
80
// LDAPOID ::= OCTET STRING -- Constrained to <numericoid>
81
//                          -- [RFC4512]
82
impl<'a> FromBer<'a, LdapError> for LdapOID<'a> {
83
6.08k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
84
        // read bytes
85
6.08k
        let (i, b) = <&[u8]>::from_ber(bytes).map_err(Err::convert)?;
86
        // convert to UTF-8
87
5.03k
        let s = std::str::from_utf8(b).or(Err(Err::Error(LdapError::InvalidDN)))?;
88
4.83k
        Ok((i, LdapOID(Cow::Borrowed(s))))
89
6.08k
    }
90
}
91
92
// URI ::= LDAPString     -- limited to characters permitted in
93
//                                -- URIs
94
#[inline]
95
32.7k
fn parse_ldap_uri(i: &[u8]) -> Result<LdapString> {
96
32.7k
    LdapString::from_ber(i)
97
32.7k
}
98
99
//
100
//
101
//
102
//
103
//
104
// ----------------------- LDAP OBJECTS -----------------------
105
//
106
//
107
//
108
//
109
//
110
//
111
112
// LDAPResult ::= SEQUENCE {
113
//      resultCode         ENUMERATED {
114
//           success                      (0),
115
//           operationsError              (1),
116
//           protocolError                (2),
117
//           timeLimitExceeded            (3),
118
//           sizeLimitExceeded            (4),
119
//           compareFalse                 (5),
120
//           compareTrue                  (6),
121
//           authMethodNotSupported       (7),
122
//           strongerAuthRequired         (8),
123
//                -- 9 reserved --
124
//           referral                     (10),
125
//           adminLimitExceeded           (11),
126
//           unavailableCriticalExtension (12),
127
//           confidentialityRequired      (13),
128
//           saslBindInProgress           (14),
129
//           noSuchAttribute              (16),
130
//           undefinedAttributeType       (17),
131
//           inappropriateMatching        (18),
132
//           constraintViolation          (19),
133
//           attributeOrValueExists       (20),
134
//           invalidAttributeSyntax       (21),
135
//                -- 22-31 unused --
136
//           noSuchObject                 (32),
137
//           aliasProblem                 (33),
138
//           invalidDNSyntax              (34),
139
//                -- 35 reserved for undefined isLeaf --
140
//           aliasDereferencingProblem    (36),
141
//                -- 37-47 unused --
142
//           inappropriateAuthentication  (48),
143
//           invalidCredentials           (49),
144
//           insufficientAccessRights     (50),
145
//           busy                         (51),
146
//           unavailable                  (52),
147
//           unwillingToPerform           (53),
148
//           loopDetect                   (54),
149
//                -- 55-63 unused --
150
//           namingViolation              (64),
151
//           objectClassViolation         (65),
152
//           notAllowedOnNonLeaf          (66),
153
//           notAllowedOnRDN              (67),
154
//           entryAlreadyExists           (68),
155
//           objectClassModsProhibited    (69),
156
//                -- 70 reserved for CLDAP --
157
//           affectsMultipleDSAs          (71),
158
//                -- 72-79 unused --
159
//           other                        (80),
160
//           ...  },
161
//      matchedDN          LDAPDN,
162
//      diagnosticMessage  LDAPString,
163
//      referral           [3] Referral OPTIONAL }
164
11.9k
fn parse_ldap_result_content(i: &[u8]) -> Result<LdapResult> {
165
11.9k
    let (i, result_code) = map(parse_ldap_enum_as_u32, ResultCode)(i)?;
166
9.95k
    let (i, matched_dn) = LdapDN::from_ber(i)?;
167
9.36k
    let (i, diagnostic_message) = LdapString::from_ber(i)?;
168
    // TODO: referral
169
9.08k
    let result = LdapResult {
170
9.08k
        result_code,
171
9.08k
        matched_dn,
172
9.08k
        diagnostic_message,
173
9.08k
    };
174
9.08k
    Ok((i, result))
175
11.9k
}
176
177
// LDAPMessage ::= SEQUENCE {
178
//      messageID       MessageID,
179
//      protocolOp      CHOICE {
180
//           bindRequest           BindRequest,
181
//           bindResponse          BindResponse,
182
//           unbindRequest         UnbindRequest,
183
//           searchRequest         SearchRequest,
184
//           searchResEntry        SearchResultEntry,
185
//           searchResDone         SearchResultDone,
186
//           searchResRef          SearchResultReference,
187
//           modifyRequest         ModifyRequest,
188
//           modifyResponse        ModifyResponse,
189
//           addRequest            AddRequest,
190
//           addResponse           AddResponse,
191
//           delRequest            DelRequest,
192
//           delResponse           DelResponse,
193
//           modDNRequest          ModifyDNRequest,
194
//           modDNResponse         ModifyDNResponse,
195
//           compareRequest        CompareRequest,
196
//           compareResponse       CompareResponse,
197
//           abandonRequest        AbandonRequest,
198
//           extendedReq           ExtendedRequest,
199
//           extendedResp          ExtendedResponse,
200
//           ...,
201
//           intermediateResponse  IntermediateResponse },
202
//      controls       [0] Controls OPTIONAL }
203
/// Parse a single LDAP message and return a structure borrowing fields from the input buffer
204
///
205
/// ```rust
206
/// use ldap_parser::FromBer;
207
/// use ldap_parser::ldap::{LdapMessage, MessageID, ProtocolOp, ProtocolOpTag};
208
///
209
/// static DATA: &[u8] = include_bytes!("../assets/message-search-request-01.bin");
210
///
211
/// # fn main() {
212
/// let res = LdapMessage::from_ber(DATA);
213
/// match res {
214
///     Ok((rem, msg)) => {
215
///         assert!(rem.is_empty());
216
///         //
217
///         assert_eq!(msg.message_id, MessageID(4));
218
///         assert_eq!(msg.protocol_op.tag(), ProtocolOpTag::SearchRequest);
219
///         match msg.protocol_op {
220
///             ProtocolOp::SearchRequest(req) => {
221
///                 assert_eq!(req.base_object.0, "dc=rccad,dc=net");
222
///             },
223
///             _ => panic!("Unexpected message type"),
224
///         }
225
///     },
226
///     _ => panic!("LDAP parsing failed: {:?}", res),
227
/// }
228
/// # }
229
/// ```
230
impl<'a> FromBer<'a, LdapError> for LdapMessage<'a> {
231
774k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
232
774k
        Sequence::from_ber_and_then(bytes, |i| {
233
707k
            let (i, message_id) = MessageID::from_ber(i)?;
234
            // read header of next element and look tag value
235
702k
            let (_, header) = Header::from_ber(i).map_err(Err::convert)?;
236
688k
            let (i, protocol_op) = match header.tag().0 {
237
9.29k
                0 => map(BindRequest::from_ber, ProtocolOp::BindRequest)(i),
238
2.24k
                1 => map(BindResponse::from_ber, ProtocolOp::BindResponse)(i),
239
272k
                2 => parse_ldap_unbind_request(i),
240
20.8k
                3 => map(SearchRequest::from_ber, ProtocolOp::SearchRequest)(i),
241
6.92k
                4 => map(SearchResultEntry::from_ber, ProtocolOp::SearchResultEntry)(i),
242
1.35k
                5 => map(parse_ldap_search_result_done, ProtocolOp::SearchResultDone)(i),
243
4.05k
                6 => map(ModifyRequest::from_ber, ProtocolOp::ModifyRequest)(i),
244
1.82k
                7 => map(parse_ldap_modify_response, ProtocolOp::ModifyResponse)(i),
245
7.83k
                8 => map(AddRequest::from_ber, ProtocolOp::AddRequest)(i),
246
1.80k
                9 => map(parse_ldap_add_response, ProtocolOp::AddResponse)(i),
247
321k
                10 => map(parse_ldap_del_request, ProtocolOp::DelRequest)(i),
248
1.12k
                11 => map(parse_ldap_del_response, ProtocolOp::DelResponse)(i),
249
2.45k
                12 => map(ModDnRequest::from_ber, ProtocolOp::ModDnRequest)(i),
250
1.12k
                13 => map(parse_ldap_moddn_response, ProtocolOp::ModDnResponse)(i),
251
2.52k
                14 => map(CompareRequest::from_ber, ProtocolOp::CompareRequest)(i),
252
1.69k
                15 => map(parse_ldap_compare_response, ProtocolOp::CompareResponse)(i),
253
2.08k
                16 => map(parse_ldap_abandon_request, ProtocolOp::AbandonRequest)(i),
254
15.6k
                19 => map(
255
15.6k
                    parse_ldap_search_result_ref,
256
15.6k
                    ProtocolOp::SearchResultReference,
257
15.6k
                )(i),
258
3.06k
                23 => map(ExtendedRequest::from_ber, ProtocolOp::ExtendedRequest)(i),
259
4.24k
                24 => map(ExtendedResponse::from_ber, ProtocolOp::ExtendedResponse)(i),
260
4.70k
                25 => map(
261
4.70k
                    IntermediateResponse::from_ber,
262
4.70k
                    ProtocolOp::IntermediateResponse,
263
4.70k
                )(i),
264
                _ => {
265
                    // print_hex_dump(i, 32);
266
                    // panic!("Protocol op {} not yet implemented", header.tag.0);
267
25
                    Err(Err::Error(LdapError::InvalidMessageType))
268
                }
269
41.4k
            }?;
270
647k
            let (i, controls) = OptTaggedParser::new(Class::ContextSpecific, Tag(0))
271
647k
                .parse_ber(i, |_, i| many0(complete(Control::from_ber))(i))?;
272
631k
            let msg = LdapMessage {
273
631k
                message_id,
274
631k
                protocol_op,
275
631k
                controls,
276
631k
            };
277
631k
            Ok((i, msg))
278
707k
        })
279
774k
    }
280
}
281
282
#[deprecated(
283
    since = "0.3.0",
284
    note = "Parsing functions are deprecated. Users should instead use the FromBer trait"
285
)]
286
#[inline]
287
0
pub fn parse_ldap_message(i: &[u8]) -> Result<LdapMessage> {
288
0
    LdapMessage::from_ber(i)
289
0
}
290
291
/// Parse a list of LDAP messages and return a structure borrowing fields from the input buffer
292
// Note: we don't use the trait because Vec<_>::from_ber forces the Error type
293
0
pub fn parse_ldap_messages(i: &[u8]) -> Result<Vec<LdapMessage>> {
294
    // println!("parse_ldap_message: len={}", i.len());
295
    // print_hex_dump(i, 32);
296
0
    many1(complete(LdapMessage::from_ber))(i)
297
0
}
298
299
// BindRequest ::= [APPLICATION 0] SEQUENCE {
300
//      version                 INTEGER (1 ..  127),
301
//      name                    LDAPDN,
302
//      authentication          AuthenticationChoice }
303
impl<'a> FromBer<'a, LdapError> for BindRequest<'a> {
304
9.29k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
305
9.29k
        TaggedParser::from_ber_and_then(Class::Application, 0, bytes, |i| {
306
            // Sequence::from_ber_and_then(bytes, |i| {
307
8.67k
            let (i, version) = verify(u8::from_ber, |&n| n < 128)(i).map_err(Err::convert)?;
308
8.29k
            let (i, name) = LdapDN::from_ber(i)?;
309
8.00k
            let (i, authentication) = AuthenticationChoice::from_ber(i)?;
310
7.11k
            let req = BindRequest {
311
7.11k
                version,
312
7.11k
                name,
313
7.11k
                authentication,
314
7.11k
            };
315
7.11k
            Ok((i, req))
316
            // })
317
8.67k
        })
318
9.29k
    }
319
}
320
321
// BindResponse ::= [APPLICATION 1] SEQUENCE {
322
//      COMPONENTS OF LDAPResult,
323
//      serverSaslCreds    [7] OCTET STRING OPTIONAL }
324
impl<'a> FromBer<'a, LdapError> for BindResponse<'a> {
325
2.24k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
326
2.24k
        TaggedParser::from_ber_and_then(Class::Application, 1, bytes, |i| {
327
1.87k
            let (i, result) = parse_ldap_result_content(i)?;
328
1.41k
            let (i, server_sasl_creds) = OptTaggedParser::new(Class::ContextSpecific, Tag(7))
329
1.41k
                .parse_ber(i, |_, data| Ok((&b""[..], Cow::Borrowed(data))))?;
330
331
            // opt(complete(parse_ber_tagged_implicit_g(7, |content, _, _| {
332
            // Ok((&b""[..], Cow::Borrowed(content)))
333
            // })))(i)?;
334
1.11k
            let req = BindResponse {
335
1.11k
                result,
336
1.11k
                server_sasl_creds,
337
1.11k
            };
338
1.11k
            Ok((i, req))
339
1.87k
        })
340
2.24k
    }
341
}
342
343
// UnbindRequest ::= [APPLICATION 2] NULL
344
272k
fn parse_ldap_unbind_request(bytes: &[u8]) -> Result<ProtocolOp> {
345
272k
    TaggedParser::from_ber_and_then(Class::Application, 2, bytes, |i| {
346
        // accept empty input, otherwise expect NULL
347
272k
        if !i.is_empty() {
348
813
            let (_, _) = <()>::from_ber(i).map_err(Err::convert)?;
349
271k
        }
350
271k
        Ok((i, ProtocolOp::UnbindRequest))
351
272k
    })
352
272k
}
353
354
// SearchRequest ::= [APPLICATION 3] SEQUENCE {
355
//      baseObject      LDAPDN,
356
//      scope           ENUMERATED {
357
//           baseObject              (0),
358
//           singleLevel             (1),
359
//           wholeSubtree            (2),
360
//           ...  },
361
//      derefAliases    ENUMERATED {
362
//           neverDerefAliases       (0),
363
//           derefInSearching        (1),
364
//           derefFindingBaseObj     (2),
365
//           derefAlways             (3) },
366
//      sizeLimit       INTEGER (0 ..  maxInt),
367
//      timeLimit       INTEGER (0 ..  maxInt),
368
//      typesOnly       BOOLEAN,
369
//      filter          Filter,
370
//      attributes      AttributeSelection }
371
impl<'a> FromBer<'a, LdapError> for SearchRequest<'a> {
372
20.8k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
373
20.8k
        TaggedParser::from_ber_and_then(Class::Application, 3, bytes, |i| {
374
19.6k
            let (i, base_object) = LdapDN::from_ber(i)?;
375
13.6k
            let (i, scope) = map(parse_ldap_enum_as_u32, SearchScope)(i)?;
376
13.2k
            let (i, deref_aliases) = map(parse_ldap_enum_as_u32, DerefAliases)(i)?;
377
12.9k
            let (i, size_limit) = parse_ldap_int_as_u32(i)?;
378
12.4k
            let (i, time_limit) = parse_ldap_int_as_u32(i)?;
379
12.2k
            let (i, types_only) = <bool>::from_ber(i).map_err(Err::convert)?;
380
11.9k
            let (i, filter) = Filter::from_ber(i)?;
381
9.49k
            let (i, attributes) = parse_attribute_selection(i)?;
382
4.47k
            let req = SearchRequest {
383
4.47k
                base_object,
384
4.47k
                scope,
385
4.47k
                deref_aliases,
386
4.47k
                size_limit,
387
4.47k
                time_limit,
388
4.47k
                types_only,
389
4.47k
                filter,
390
4.47k
                attributes,
391
4.47k
            };
392
4.47k
            Ok((i, req))
393
19.6k
        })
394
20.8k
    }
395
}
396
397
// SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
398
//     objectName      LDAPDN,
399
//     attributes      PartialAttributeList }
400
impl<'a> FromBer<'a, LdapError> for SearchResultEntry<'a> {
401
6.92k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
402
6.92k
        TaggedParser::from_ber_and_then(Class::Application, 4, bytes, |i| {
403
4.88k
            let (i, object_name) = LdapDN::from_ber(i)?;
404
4.60k
            let (i, attributes) = parse_partial_attribute_list(i)?;
405
4.35k
            let res = SearchResultEntry {
406
4.35k
                object_name,
407
4.35k
                attributes,
408
4.35k
            };
409
4.35k
            Ok((i, res))
410
4.88k
        })
411
6.92k
    }
412
}
413
414
// SearchResultDone ::= [APPLICATION 5] LDAPResult
415
1.35k
fn parse_ldap_search_result_done(bytes: &[u8]) -> Result<LdapResult> {
416
1.35k
    TaggedParser::from_ber_and_then(Class::Application, 5, bytes, parse_ldap_result_content)
417
1.35k
}
418
419
// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
420
//     object          LDAPDN,
421
//     changes         SEQUENCE OF change SEQUENCE {
422
//          operation       ENUMERATED {
423
//               add     (0),
424
//               delete  (1),
425
//               replace (2),
426
//               ...  },
427
//          modification    PartialAttribute } }
428
impl<'a> FromBer<'a, LdapError> for ModifyRequest<'a> {
429
4.05k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
430
4.05k
        TaggedParser::from_ber_and_then(Class::Application, 6, bytes, |i| {
431
3.69k
            let (i, object) = LdapDN::from_ber(i)?;
432
3.37k
            let (i, changes) = Sequence::from_ber_and_then(i, many1(complete(Change::from_ber)))?;
433
3.06k
            let res = ModifyRequest { object, changes };
434
3.06k
            Ok((i, res))
435
3.69k
        })
436
4.05k
    }
437
}
438
439
// ModifyResponse ::= [APPLICATION 7] LDAPResult
440
1.82k
fn parse_ldap_modify_response(bytes: &[u8]) -> Result<ModifyResponse> {
441
1.82k
    TaggedParser::from_ber_and_then(Class::Application, 7, bytes, |i| {
442
1.50k
        let (i, result) = parse_ldap_result_content(i)?;
443
618
        let res = ModifyResponse { result };
444
618
        Ok((i, res))
445
1.50k
    })
446
1.82k
}
447
448
// AddRequest ::= [APPLICATION 8] SEQUENCE {
449
//     entry           LDAPDN,
450
//     attributes      AttributeList }
451
impl<'a> FromBer<'a, LdapError> for AddRequest<'a> {
452
7.83k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
453
7.83k
        TaggedParser::from_ber_and_then(Class::Application, 8, bytes, |i| {
454
7.54k
            let (i, entry) = LdapDN::from_ber(i)?;
455
5.62k
            let (i, attributes) = parse_attribute_list(i)?;
456
4.32k
            let res = AddRequest { entry, attributes };
457
4.32k
            Ok((i, res))
458
7.54k
        })
459
7.83k
    }
460
}
461
462
// AddResponse ::= [APPLICATION 9] LDAPResult
463
1.80k
fn parse_ldap_add_response(bytes: &[u8]) -> Result<LdapResult> {
464
1.80k
    TaggedParser::from_ber_and_then(Class::Application, 9, bytes, parse_ldap_result_content)
465
1.80k
}
466
467
// DelRequest ::= [APPLICATION 10] LDAPDN
468
321k
fn parse_ldap_del_request(bytes: &[u8]) -> Result<LdapDN> {
469
321k
    TaggedParser::from_ber_and_then(Class::Application, 10, bytes, |i| {
470
320k
        let s = std::str::from_utf8(i).or(Err(Err::Error(LdapError::InvalidDN)))?;
471
320k
        let oid = LdapDN(Cow::Borrowed(s));
472
320k
        Ok((&b""[..], oid))
473
320k
    })
474
321k
}
475
476
// DelResponse ::= [APPLICATION 11] LDAPResult
477
1.12k
fn parse_ldap_del_response(bytes: &[u8]) -> Result<LdapResult> {
478
1.12k
    TaggedParser::from_ber_and_then(Class::Application, 11, bytes, parse_ldap_result_content)
479
1.12k
}
480
481
// ModifyDNRequest ::= [APPLICATION 12] SEQUENCE {
482
//     entry           LDAPDN,
483
//     newrdn          RelativeLDAPDN,
484
//     deleteoldrdn    BOOLEAN,
485
//     newSuperior     [0] LDAPDN OPTIONAL }
486
impl<'a> FromBer<'a, LdapError> for ModDnRequest<'a> {
487
2.45k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
488
2.45k
        TaggedParser::from_ber_and_then(Class::Application, 12, bytes, |i| {
489
2.08k
            let (i, entry) = LdapDN::from_ber(i)?;
490
1.84k
            let (i, newrdn) = RelativeLdapDN::from_ber(i)?;
491
1.57k
            let (i, deleteoldrdn) = <bool>::from_ber(i).map_err(Err::convert)?;
492
1.05k
            let (i, newsuperior) =
493
1.31k
                OptTaggedParser::new(Class::ContextSpecific, Tag(0)).parse_ber(i, |_, i| {
494
330
                    let s = std::str::from_utf8(i).or(Err(Err::Error(LdapError::InvalidDN)))?;
495
328
                    let oid = LdapDN(Cow::Borrowed(s));
496
328
                    Ok((&b""[..], oid))
497
330
                })?;
498
1.05k
            let res = ModDnRequest {
499
1.05k
                entry,
500
1.05k
                newrdn,
501
1.05k
                deleteoldrdn,
502
1.05k
                newsuperior,
503
1.05k
            };
504
1.05k
            Ok((i, res))
505
2.08k
        })
506
2.45k
    }
507
}
508
509
// ModifyDNResponse ::= [APPLICATION 13] LDAPResult
510
1.12k
fn parse_ldap_moddn_response(bytes: &[u8]) -> Result<LdapResult> {
511
1.12k
    TaggedParser::from_ber_and_then(Class::Application, 13, bytes, parse_ldap_result_content)
512
1.12k
}
513
514
// CompareRequest ::= [APPLICATION 14] SEQUENCE {
515
//     entry           LDAPDN,
516
//     ava             AttributeValueAssertion }
517
impl<'a> FromBer<'a, LdapError> for CompareRequest<'a> {
518
2.52k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
519
2.52k
        TaggedParser::from_ber_and_then(Class::Application, 14, bytes, |i| {
520
1.88k
            let (i, entry) = LdapDN::from_ber(i)?;
521
1.60k
            let (i, ava) = AttributeValueAssertion::from_ber(i)?;
522
490
            let res = CompareRequest { entry, ava };
523
490
            Ok((i, res))
524
1.88k
        })
525
2.52k
    }
526
}
527
528
// CompareResponse ::= [APPLICATION 15] LDAPResult
529
1.69k
fn parse_ldap_compare_response(bytes: &[u8]) -> Result<LdapResult> {
530
1.69k
    TaggedParser::from_ber_and_then(Class::Application, 15, bytes, parse_ldap_result_content)
531
1.69k
}
532
533
// AbandonRequest ::= [APPLICATION 16] MessageID
534
2.08k
fn parse_ldap_abandon_request(bytes: &[u8]) -> Result<MessageID> {
535
2.08k
    let (rem, id) = TaggedValue::<u32, _, Implicit, { Class::APPLICATION }, 16>::from_ber(bytes)
536
2.08k
        .map_err(Err::convert)?;
537
1.51k
    Ok((rem, MessageID(id.into_inner())))
538
2.08k
}
539
540
// SearchResultReference ::= [APPLICATION 19] SEQUENCE
541
//                                   SIZE (1..MAX) OF uri URI
542
15.6k
fn parse_ldap_search_result_ref(bytes: &[u8]) -> Result<Vec<LdapString>> {
543
15.6k
    TaggedParser::from_ber_and_then(
544
15.6k
        Class::Application,
545
        19,
546
15.6k
        bytes,
547
15.6k
        many1(complete(parse_ldap_uri)),
548
    )
549
15.6k
}
550
551
// ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
552
//     requestName      [0] LDAPOID,
553
//     requestValue     [1] OCTET STRING OPTIONAL }
554
impl<'a> FromBer<'a, LdapError> for ExtendedRequest<'a> {
555
3.06k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
556
3.06k
        TaggedParser::from_ber_and_then(Class::Application, 23, bytes, |i| {
557
1.80k
            let (i, request_name) =
558
2.30k
                TaggedParser::from_ber_and_then(Class::ContextSpecific, 0, i, |i| {
559
1.81k
                    let s = std::str::from_utf8(i).or(Err(Err::Error(LdapError::InvalidDN)))?;
560
1.80k
                    let oid = LdapOID(Cow::Borrowed(s));
561
1.80k
                    Ok((&b""[..], oid))
562
1.81k
                })?;
563
1.80k
            let (i, request_value) = OptTaggedParser::new(Class::ContextSpecific, Tag(1))
564
1.80k
                .parse_ber(i, |_, data| Ok((&b""[..], Cow::Borrowed(data))))?;
565
1.59k
            let req = ExtendedRequest {
566
1.59k
                request_name,
567
1.59k
                request_value,
568
1.59k
            };
569
1.59k
            Ok((i, req))
570
2.30k
        })
571
3.06k
    }
572
}
573
574
// ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
575
//     COMPONENTS OF LDAPResult,
576
//     responseName     [10] LDAPOID OPTIONAL,
577
//     responseValue    [11] OCTET STRING OPTIONAL }
578
impl<'a> FromBer<'a, LdapError> for ExtendedResponse<'a> {
579
4.24k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
580
4.24k
        TaggedParser::from_ber_and_then(Class::Application, 24, bytes, |i| {
581
3.95k
            let (i, result) = parse_ldap_result_content(i)?;
582
3.43k
            let (i, response_name) = OptTaggedParser::new(Class::ContextSpecific, Tag(10))
583
3.43k
                .parse_ber(i, |_, i| {
584
433
                    let s = std::str::from_utf8(i).or(Err(Err::Error(LdapError::InvalidDN)))?;
585
423
                    let oid = LdapOID(Cow::Borrowed(s));
586
423
                    Ok((&b""[..], oid))
587
433
                })?;
588
3.22k
            let (i, response_value) = OptTaggedParser::new(Class::ContextSpecific, Tag(11))
589
3.22k
                .parse_ber(i, |_, data| Ok((&b""[..], Cow::Borrowed(data))))?;
590
3.02k
            let resp = ExtendedResponse {
591
3.02k
                result,
592
3.02k
                response_name,
593
3.02k
                response_value,
594
3.02k
            };
595
3.02k
            Ok((i, resp))
596
3.95k
        })
597
4.24k
    }
598
}
599
600
// IntermediateResponse ::= [APPLICATION 25] SEQUENCE {
601
//      responseName     [0] LDAPOID OPTIONAL,
602
//      responseValue    [1] OCTET STRING OPTIONAL }
603
impl<'a> FromBer<'a, LdapError> for IntermediateResponse<'a> {
604
4.70k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
605
4.70k
        TaggedParser::from_ber_and_then(Class::Application, 25, bytes, |i| {
606
4.22k
            let (i, response_name) = OptTaggedParser::new(Class::ContextSpecific, Tag(0))
607
4.22k
                .parse_ber(i, |_, i| {
608
474
                    let s = std::str::from_utf8(i).or(Err(Err::Error(LdapError::InvalidDN)))?;
609
472
                    let oid = LdapOID(Cow::Borrowed(s));
610
472
                    Ok((&b""[..], oid))
611
474
                })?;
612
3.94k
            let (i, response_value) = OptTaggedParser::new(Class::ContextSpecific, Tag(1))
613
3.94k
                .parse_ber(i, |_, data| Ok((&b""[..], Cow::Borrowed(data))))?;
614
3.74k
            let resp = IntermediateResponse {
615
3.74k
                response_name,
616
3.74k
                response_value,
617
3.74k
            };
618
3.74k
            Ok((i, resp))
619
4.22k
        })
620
4.70k
    }
621
}
622
623
// AuthenticationChoice ::= CHOICE {
624
//      simple                  [0] OCTET STRING,
625
//                              -- 1 and 2 reserved
626
//      sasl                    [3] SaslCredentials,
627
//      ...  }
628
impl<'a> FromBer<'a, LdapError> for AuthenticationChoice<'a> {
629
8.00k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
630
8.00k
        let (rem, header) = Header::from_ber(bytes).map_err(Err::convert)?;
631
7.76k
        match header.tag().0 {
632
            0 => {
633
                // assume len is primitive, and just take bytes
634
6.58k
                let sz = header
635
6.58k
                    .length()
636
6.58k
                    .definite()
637
6.58k
                    .map_err(|e| Err::Error(LdapError::Ber(e)))?;
638
6.58k
                let (i, b) = take(sz)(rem)?;
639
                // // other solution: read content as octetstring and get slice
640
                // let (i, b) = map_res(
641
                //     |d| {
642
                //         ber_read_element_content_as(
643
                //             d,
644
                //             BerTag::OctetString,
645
                //             header.len,
646
                //             header.is_constructed(),
647
                //             1,
648
                //         )
649
                //     },
650
                //     |o| o.as_slice(),
651
                // )(rem)
652
                // .map_err(Err::convert)?;
653
6.30k
                Ok((i, AuthenticationChoice::Simple(Cow::Borrowed(b))))
654
            }
655
1.00k
            3 => map(parse_sasl_credentials, AuthenticationChoice::Sasl)(rem),
656
177
            _ => Err(Err::Error(LdapError::InvalidAuthenticationType)),
657
        }
658
8.00k
    }
659
}
660
661
// SaslCredentials ::= SEQUENCE {
662
//      mechanism               LDAPString,
663
//      credentials             OCTET STRING OPTIONAL }
664
1.00k
fn parse_sasl_credentials(i: &[u8]) -> Result<SaslCredentials> {
665
1.00k
    let (i, mechanism) = LdapString::from_ber(i)?;
666
805
    let (i, credentials) = opt(complete(map(
667
805
        parse_ldap_octet_string_as_slice,
668
805
        Cow::Borrowed,
669
805
    )))(i)?;
670
805
    let credentials = SaslCredentials {
671
805
        mechanism,
672
805
        credentials,
673
805
    };
674
805
    Ok((i, credentials))
675
1.00k
}
676
677
// AttributeSelection ::= SEQUENCE OF selector LDAPString
678
//      -- The LDAPString is constrained to
679
//      -- <attributeSelector> in Section 4.5.1.8
680
9.49k
fn parse_attribute_selection(bytes: &[u8]) -> Result<Vec<LdapString>> {
681
9.49k
    Sequence::from_ber_and_then(bytes, many0(complete(LdapString::from_ber)))
682
9.49k
}
683
684
// PartialAttributeList ::= SEQUENCE OF partialAttribute PartialAttribute
685
4.60k
fn parse_partial_attribute_list(bytes: &[u8]) -> Result<Vec<PartialAttribute>> {
686
4.60k
    Sequence::from_ber_and_then(bytes, many0(complete(PartialAttribute::from_ber)))
687
4.60k
}
688
689
// AttributeList ::= SEQUENCE OF attribute Attribute
690
5.62k
fn parse_attribute_list(bytes: &[u8]) -> Result<Vec<Attribute>> {
691
5.62k
    Sequence::from_ber_and_then(bytes, many0(complete(Attribute::from_ber)))
692
5.62k
}
693
694
// change SEQUENCE {
695
//          operation       ENUMERATED {
696
//               add     (0),
697
//               delete  (1),
698
//               replace (2),
699
//               ...  },
700
//          modification    PartialAttribute }
701
impl<'a> FromBer<'a, LdapError> for Change<'a> {
702
7.42k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
703
7.42k
        Sequence::from_ber_and_then(bytes, |i| {
704
5.89k
            let (i, operation) = map(parse_ldap_enum_as_u32, Operation)(i)?;
705
4.60k
            let (i, modification) = PartialAttribute::from_ber(i)?;
706
4.31k
            let change = Change {
707
4.31k
                operation,
708
4.31k
                modification,
709
4.31k
            };
710
4.31k
            Ok((i, change))
711
5.89k
        })
712
7.42k
    }
713
}
714
715
// Control ::= SEQUENCE {
716
//     controlType             LDAPOID,
717
//     criticality             BOOLEAN DEFAULT FALSE,
718
//     controlValue            OCTET STRING OPTIONAL }
719
impl<'a> FromBer<'a, LdapError> for Control<'a> {
720
16.4k
    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, LdapError> {
721
16.4k
        Sequence::from_ber_and_then(bytes, |i| {
722
6.08k
            let (i, control_type) = LdapOID::from_ber(i)?;
723
4.83k
            let (i, maybe_critical) = <Option<bool>>::from_ber(i).map_err(Err::convert)?;
724
            // opt(complete(bool::from_ber))(i).map_err(Err::convert)?;
725
2.62k
            let criticality = maybe_critical.unwrap_or(false);
726
2.62k
            let (i, control_value) = opt(complete(map(
727
2.62k
                parse_ldap_octet_string_as_slice,
728
2.62k
                Cow::Borrowed,
729
2.62k
            )))(i)?;
730
2.62k
            let control = Control {
731
2.62k
                control_type,
732
2.62k
                criticality,
733
2.62k
                control_value,
734
2.62k
            };
735
2.62k
            Ok((i, control))
736
6.08k
        })
737
16.4k
    }
738
}
739
740
//
741
//
742
//
743
//
744
//
745
// ----------------------- TESTS -----------------------
746
//
747
//
748
//
749
//
750
//
751
//
752
753
#[cfg(test)]
754
mod tests {
755
    use super::*;
756
    use asn1_rs::oid;
757
    use hex_literal::hex;
758
759
    #[test]
760
    fn test_parse_bind_request() {
761
        const DATA: &[u8] = include_bytes!("../assets/bind_request.bin");
762
763
        let (rem, req) = BindRequest::from_ber(DATA).expect("parsing failed");
764
        //
765
        // dbg!(&req);
766
        //
767
        assert!(rem.is_empty());
768
        assert_eq!(&req.name.0, "xxxxxxxxxxx@xx.xxx.xxxxx.net");
769
        assert_eq!(
770
            req.authentication,
771
            AuthenticationChoice::Simple(Cow::Borrowed(b"passwor8d1"))
772
        );
773
    }
774
775
    #[test]
776
    fn test_parse_bind_request_sasl() {
777
        const DATA: &[u8] = include_bytes!("../assets/bind_request_sasl.bin");
778
779
        let (rem, req) = BindRequest::from_ber(DATA).expect("parsing failed");
780
        //
781
        // dbg!(&req);
782
        //
783
        assert!(rem.is_empty());
784
        assert_eq!(&req.name.0, "");
785
        if let AuthenticationChoice::Sasl(sasl_credentials) = &req.authentication {
786
            assert_eq!(&sasl_credentials.mechanism.0, "GSS-SPNEGO");
787
        } else {
788
            panic!("wrong authentication type");
789
        }
790
    }
791
792
    #[test]
793
    fn test_parse_bind_response_minimal() {
794
        const DATA: &[u8] = &hex!("61 84 00 00 00 07 0a 01 00 04 00 04 00");
795
        let (rem, resp) = BindResponse::from_ber(DATA).expect("parsing failed");
796
        //
797
        // dbg!(&resp);
798
        //
799
        assert!(rem.is_empty());
800
        assert_eq!(resp.result.result_code, ResultCode::Success);
801
    }
802
803
    #[test]
804
    fn test_parse_bind_response_sasl() {
805
        const DATA: &[u8] = include_bytes!("../assets/bind_response_sasl.bin");
806
        let (rem, resp) = BindResponse::from_ber(DATA).expect("parsing failed");
807
        //
808
        // dbg!(&resp);
809
        //
810
        assert!(rem.is_empty());
811
        assert_eq!(resp.result.result_code, ResultCode::Success);
812
        assert!(resp.server_sasl_creds.is_some());
813
    }
814
815
    #[test]
816
    fn test_parse_unbind_request() {
817
        const DATA: &[u8] = &hex!("42 00");
818
819
        let (rem, req) = parse_ldap_unbind_request(DATA).expect("parsing failed");
820
        //
821
        // dbg!(&req);
822
        //
823
        assert!(rem.is_empty());
824
        assert_eq!(req, ProtocolOp::UnbindRequest);
825
    }
826
827
    #[test]
828
    fn test_parse_search_request() {
829
        const DATA: &[u8] = include_bytes!("../assets/search_request.bin");
830
        let (rem, resp) = SearchRequest::from_ber(DATA).expect("parsing failed");
831
        //
832
        // dbg!(&resp);
833
        //
834
        assert!(rem.is_empty());
835
        assert_eq!(&resp.base_object.0, "DC=xx,DC=xxx,DC=xxxxx,DC=net");
836
        assert_eq!(resp.scope, SearchScope::WholeSubtree);
837
        assert_eq!(resp.attributes.len(), 1);
838
    }
839
840
    #[test]
841
    fn test_parse_search_result_entry() {
842
        const DATA: &[u8] = include_bytes!("../assets/search_result_entry.bin");
843
        let (rem, resp) = SearchResultEntry::from_ber(DATA).expect("parsing failed");
844
        //
845
        // dbg!(&resp);
846
        //
847
        assert!(rem.is_empty());
848
        assert_eq!(resp.attributes.len(), 1);
849
    }
850
851
    #[test]
852
    fn test_parse_search_result_done() {
853
        const DATA: &[u8] = include_bytes!("../assets/search_result_done.bin");
854
        let (rem, resp) = parse_ldap_search_result_done(DATA).expect("parsing failed");
855
        //
856
        // dbg!(&resp);
857
        //
858
        assert!(rem.is_empty());
859
        assert_eq!(resp.result_code, ResultCode::Success);
860
    }
861
862
    #[test]
863
    fn test_parse_search_result_ref() {
864
        const DATA: &[u8] = include_bytes!("../assets/search_result_ref.bin");
865
        let (rem, v) = parse_ldap_search_result_ref(DATA).expect("parsing failed");
866
        //
867
        // dbg!(&v);
868
        //
869
        assert!(rem.is_empty());
870
        assert_eq!(v.len(), 1);
871
        assert_eq!(
872
            &v[0].0,
873
            "ldap://DomainDnsZones.rccad.net/DC=DomainDnsZones,DC=rccad,DC=net"
874
        );
875
    }
876
877
    #[test]
878
    fn test_parse_extended_req() {
879
        const DATA: &[u8] = include_bytes!("../assets/extended-req.bin");
880
        let (rem, req) = ExtendedRequest::from_ber(DATA).expect("parsing failed");
881
        //
882
        // dbg!(&req);
883
        //
884
        assert!(rem.is_empty());
885
        assert_eq!(
886
            req.request_name.0,
887
            oid!(1.3.6 .1 .4 .1 .1466 .20037).to_string()
888
        );
889
        assert!(req.request_value.is_none());
890
    }
891
892
    #[test]
893
    fn test_parse_extended_response() {
894
        const DATA: &[u8] = &hex!("78 07 0a 01 00 04 00 04 00");
895
        let (rem, resp) = ExtendedResponse::from_ber(DATA).expect("parsing failed");
896
        //
897
        // dbg!(&resp);
898
        //
899
        assert!(rem.is_empty());
900
        assert_eq!(resp.result.result_code, ResultCode::Success);
901
    }
902
903
    #[test]
904
    fn test_parse_modify_request() {
905
        const DATA: &[u8] = include_bytes!("../assets/modify-request.bin");
906
        let (rem, req) = ModifyRequest::from_ber(DATA).expect("parsing failed");
907
        //
908
        // dbg!(&req);
909
        //
910
        assert!(rem.is_empty());
911
        assert_eq!(&req.object.0, "cn=username1,ou=users,dc=xxx,dc=internet");
912
        assert_eq!(req.changes.len(), 1);
913
        assert_eq!(req.changes[0].modification.attr_type.0, "description");
914
    }
915
916
    #[test]
917
    fn test_parse_modify_response() {
918
        const DATA: &[u8] = include_bytes!("../assets/modify-response.bin");
919
        let (rem, resp) = parse_ldap_modify_response(DATA).expect("parsing failed");
920
        //
921
        // dbg!(&resp);
922
        //
923
        assert!(rem.is_empty());
924
        assert_eq!(resp.result.result_code, ResultCode::Success);
925
    }
926
927
    #[test]
928
    fn test_parse_add_request() {
929
        const DATA: &[u8] = include_bytes!("../assets/add-request.bin");
930
        let (rem, req) = AddRequest::from_ber(DATA).expect("parsing failed");
931
        //
932
        // dbg!(&req);
933
        //
934
        assert!(rem.is_empty());
935
        assert_eq!(&req.entry.0, "cn=username1,ou=users,dc=xxx,dc=internet");
936
        assert_eq!(req.attributes.len(), 4);
937
    }
938
939
    #[test]
940
    fn test_parse_add_response() {
941
        const DATA: &[u8] = include_bytes!("../assets/add-response.bin");
942
        let (rem, resp) = parse_ldap_add_response(DATA).expect("parsing failed");
943
        //
944
        // dbg!(&resp);
945
        //
946
        assert!(rem.is_empty());
947
        assert_eq!(resp.result_code, ResultCode::Success);
948
    }
949
950
    #[test]
951
    fn test_parse_del_request() {
952
        const DATA: &[u8] = include_bytes!("../assets/del-request.bin");
953
        let (rem, req) = parse_ldap_del_request(DATA).expect("parsing failed");
954
        //
955
        // dbg!(&req);
956
        //
957
        assert!(rem.is_empty());
958
        assert_eq!(&req.0, "cn=username2,ou=users2,dc=xxx,dc=internet");
959
    }
960
961
    #[test]
962
    fn test_parse_del_response() {
963
        const DATA: &[u8] = include_bytes!("../assets/del-response.bin");
964
        let (rem, resp) = parse_ldap_del_response(DATA).expect("parsing failed");
965
        //
966
        // dbg!(&resp);
967
        //
968
        assert!(rem.is_empty());
969
        assert_eq!(resp.result_code, ResultCode::Success);
970
    }
971
972
    #[test]
973
    fn test_parse_moddn_request() {
974
        const DATA: &[u8] = include_bytes!("../assets/moddn-request.bin");
975
        let (rem, req) = ModDnRequest::from_ber(DATA).expect("parsing failed");
976
        //
977
        // dbg!(&req);
978
        //
979
        assert!(rem.is_empty());
980
        assert_eq!(&req.entry.0, "cn=username1,ou=users,dc=xxx,dc=internet");
981
        assert_eq!(&req.newrdn.0, "cn=username2");
982
        assert!(req.deleteoldrdn);
983
        assert_eq!(&req.newsuperior.unwrap().0, "ou=users,dc=xxx,dc=internet");
984
    }
985
986
    #[test]
987
    fn test_parse_moddn_response() {
988
        const DATA: &[u8] = include_bytes!("../assets/moddn-response.bin");
989
        let (rem, resp) = parse_ldap_moddn_response(DATA).expect("parsing failed");
990
        //
991
        // dbg!(&resp);
992
        //
993
        assert!(rem.is_empty());
994
        assert_eq!(resp.result_code, ResultCode::Success);
995
    }
996
997
    #[test]
998
    fn test_parse_compare_request() {
999
        const DATA: &[u8] = include_bytes!("../assets/compare-request.bin");
1000
        let (rem, req) = CompareRequest::from_ber(DATA).expect("parsing failed");
1001
        //
1002
        // dbg!(&req);
1003
        //
1004
        assert!(rem.is_empty());
1005
        assert_eq!(&req.entry.0, "cn=username2,ou=users2,dc=xxx,dc=internet");
1006
        assert_eq!(&req.ava.attribute_desc.0, "cn");
1007
    }
1008
1009
    #[test]
1010
    fn test_parse_compare_response() {
1011
        const DATA: &[u8] = include_bytes!("../assets/compare-response.bin");
1012
        let (rem, resp) = parse_ldap_compare_response(DATA).expect("parsing failed");
1013
        //
1014
        // dbg!(&resp);
1015
        //
1016
        assert!(rem.is_empty());
1017
        assert_eq!(resp.result_code, ResultCode::CompareTrue);
1018
    }
1019
1020
    #[test]
1021
    fn test_parse_abandon_request() {
1022
        const DATA: &[u8] = &[0x30, 0x06, 0x02, 0x01, 0x06, 0x50, 0x01, 0x05];
1023
1024
        let (rem, msg) = LdapMessage::from_ber(DATA).expect("parsing failed");
1025
        assert!(rem.is_empty());
1026
        assert_eq!(msg.message_id, MessageID(6));
1027
        assert!(matches!(
1028
            msg.protocol_op,
1029
            ProtocolOp::AbandonRequest(MessageID(5))
1030
        ))
1031
    }
1032
}