Coverage Report

Created: 2025-11-16 07:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PcapPlusPlus/Packet++/src/LdapLayer.cpp
Line
Count
Source
1
#include "LdapLayer.h"
2
#include "GeneralUtils.h"
3
#include <unordered_map>
4
5
namespace pcpp
6
{
7
8
  constexpr uint8_t LdapResponseLayer::referralTagType;
9
  constexpr int LdapBindResponseLayer::serverSaslCredentialsTagType;
10
11
  // region LdapOperationType
12
13
  // clang-format off
14
  static const std::unordered_map<LdapOperationType::Value, std::string, EnumClassHash<LdapOperationType::Value>> LdapOperationTypeToString{
15
    { LdapOperationType::BindRequest,           "BindRequest"           },
16
    { LdapOperationType::BindResponse,          "BindResponse"          },
17
    { LdapOperationType::UnbindRequest,         "UnbindRequest"         },
18
    { LdapOperationType::SearchRequest,         "SearchRequest"         },
19
    { LdapOperationType::SearchResultEntry,     "SearchResultEntry"     },
20
    { LdapOperationType::SearchResultDone,      "SearchResultDone"      },
21
    { LdapOperationType::ModifyRequest,         "ModifyRequest"         },
22
    { LdapOperationType::ModifyResponse,        "ModifyResponse"        },
23
    { LdapOperationType::AddRequest,            "AddRequest"            },
24
    { LdapOperationType::AddResponse,           "AddResponse"           },
25
    { LdapOperationType::DeleteRequest,         "DeleteRequest"         },
26
    { LdapOperationType::DeleteResponse,        "DeleteResponse"        },
27
    { LdapOperationType::ModifyDNRequest,       "ModifyDNRequest"       },
28
    { LdapOperationType::ModifyDNResponse,      "ModifyDNResponse"      },
29
    { LdapOperationType::CompareRequest,        "CompareRequest"        },
30
    { LdapOperationType::CompareResponse,       "CompareResponse"       },
31
    { LdapOperationType::AbandonRequest,        "AbandonRequest"        },
32
    { LdapOperationType::SearchResultReference, "SearchResultReference" },
33
    { LdapOperationType::ExtendedRequest,       "ExtendedRequest"       },
34
    { LdapOperationType::ExtendedResponse,      "ExtendedResponse"      },
35
    { LdapOperationType::IntermediateResponse,  "IntermediateResponse"  },
36
    { LdapOperationType::Unknown,               "Unknown"               }
37
  };
38
  // clang-format on
39
40
  static const std::unordered_map<uint8_t, LdapOperationType> UintToLdapOperationType{
41
    { static_cast<uint8_t>(LdapOperationType::BindRequest),           LdapOperationType::BindRequest           },
42
    { static_cast<uint8_t>(LdapOperationType::BindResponse),          LdapOperationType::BindResponse          },
43
    { static_cast<uint8_t>(LdapOperationType::UnbindRequest),         LdapOperationType::UnbindRequest         },
44
    { static_cast<uint8_t>(LdapOperationType::SearchRequest),         LdapOperationType::SearchRequest         },
45
    { static_cast<uint8_t>(LdapOperationType::SearchResultEntry),     LdapOperationType::SearchResultEntry     },
46
    { static_cast<uint8_t>(LdapOperationType::SearchResultDone),      LdapOperationType::SearchResultDone      },
47
    { static_cast<uint8_t>(LdapOperationType::ModifyResponse),        LdapOperationType::ModifyResponse        },
48
    { static_cast<uint8_t>(LdapOperationType::AddRequest),            LdapOperationType::AddRequest            },
49
    { static_cast<uint8_t>(LdapOperationType::AddResponse),           LdapOperationType::AddResponse           },
50
    { static_cast<uint8_t>(LdapOperationType::DeleteRequest),         LdapOperationType::DeleteRequest         },
51
    { static_cast<uint8_t>(LdapOperationType::DeleteResponse),        LdapOperationType::DeleteResponse        },
52
    { static_cast<uint8_t>(LdapOperationType::ModifyDNRequest),       LdapOperationType::ModifyDNRequest       },
53
    { static_cast<uint8_t>(LdapOperationType::ModifyDNResponse),      LdapOperationType::ModifyDNResponse      },
54
    { static_cast<uint8_t>(LdapOperationType::CompareRequest),        LdapOperationType::CompareRequest        },
55
    { static_cast<uint8_t>(LdapOperationType::CompareResponse),       LdapOperationType::CompareResponse       },
56
    { static_cast<uint8_t>(LdapOperationType::AbandonRequest),        LdapOperationType::AbandonRequest        },
57
    { static_cast<uint8_t>(LdapOperationType::SearchResultReference), LdapOperationType::SearchResultReference },
58
    { static_cast<uint8_t>(LdapOperationType::ExtendedRequest),       LdapOperationType::ExtendedRequest       },
59
    { static_cast<uint8_t>(LdapOperationType::ExtendedResponse),      LdapOperationType::ExtendedResponse      },
60
    { static_cast<uint8_t>(LdapOperationType::IntermediateResponse),  LdapOperationType::IntermediateResponse  }
61
  };
62
63
  std::string LdapOperationType::toString() const
64
16.0k
  {
65
16.0k
    return LdapOperationTypeToString.at(m_Value);
66
16.0k
  }
67
68
  LdapOperationType LdapOperationType::fromUintValue(uint8_t value)
69
66.1k
  {
70
66.1k
    auto result = UintToLdapOperationType.find(value);
71
66.1k
    if (result != UintToLdapOperationType.end())
72
65.6k
    {
73
65.6k
      return result->second;
74
65.6k
    }
75
76
468
    return LdapOperationType::Unknown;
77
66.1k
  }
78
79
  // endregion
80
81
  // region LdapResultCode
82
83
  // clang-format off
84
  static const std::unordered_map<LdapResultCode::Value, std::string, EnumClassHash<LdapResultCode::Value>> LdapResultCodeToString{
85
    { LdapResultCode::Success,                      "Success"                      },
86
    { LdapResultCode::OperationsError,              "OperationsError"              },
87
    { LdapResultCode::ProtocolError,                "ProtocolError"                },
88
    { LdapResultCode::TimeLimitExceeded,            "TimeLimitExceeded"            },
89
    { LdapResultCode::SizeLimitExceeded,            "SizeLimitExceeded"            },
90
    { LdapResultCode::CompareFalse,                 "CompareFalse"                 },
91
    { LdapResultCode::CompareTrue,                  "CompareTrue"                  },
92
    { LdapResultCode::AuthMethodNotSupported,       "AuthMethodNotSupported"       },
93
    { LdapResultCode::StrongerAuthRequired,         "StrongerAuthRequired"         },
94
    { LdapResultCode::Referral,                     "Referral"                     },
95
    { LdapResultCode::AdminLimitExceeded,           "AdminLimitExceeded"           },
96
    { LdapResultCode::UnavailableCriticalExtension, "UnavailableCriticalExtension" },
97
    { LdapResultCode::ConfidentialityRequired,      "ConfidentialityRequired"      },
98
    { LdapResultCode::SaslBindInProgress,           "SaslBindInProgress"           },
99
    { LdapResultCode::NoSuchAttribute,              "NoSuchAttribute"              },
100
    { LdapResultCode::UndefinedAttributeType,       "UndefinedAttributeType"       },
101
    { LdapResultCode::InappropriateMatching,        "InappropriateMatching"        },
102
    { LdapResultCode::ConstraintViolation,          "ConstraintViolation"          },
103
    { LdapResultCode::AttributeOrValueExists,       "AttributeOrValueExists"       },
104
    { LdapResultCode::InvalidAttributeSyntax,       "InvalidAttributeSyntax"       },
105
    { LdapResultCode::NoSuchObject,                 "NoSuchObject"                 },
106
    { LdapResultCode::AliasProblem,                 "AliasProblem"                 },
107
    { LdapResultCode::InvalidDNSyntax,              "InvalidDNSyntax"              },
108
    { LdapResultCode::AliasDereferencingProblem,    "AliasDereferencingProblem"    },
109
    { LdapResultCode::InappropriateAuthentication,  "InappropriateAuthentication"  },
110
    { LdapResultCode::InvalidCredentials,           "InvalidCredentials"           },
111
    { LdapResultCode::InsufficientAccessRights,     "InsufficientAccessRights"     },
112
    { LdapResultCode::Busy,                         "Busy"                         },
113
    { LdapResultCode::Unavailable,                  "Unavailable"                  },
114
    { LdapResultCode::UnwillingToPerform,           "UnwillingToPerform"           },
115
    { LdapResultCode::LoopDetect,                   "LoopDetect"                   },
116
    { LdapResultCode::NamingViolation,              "NamingViolation"              },
117
    { LdapResultCode::ObjectClassViolation,         "ObjectClassViolation"         },
118
    { LdapResultCode::NotAllowedOnNonLeaf,          "NotAllowedOnNonLeaf"          },
119
    { LdapResultCode::NotAllowedOnRDN,              "NotAllowedOnRDN"              },
120
    { LdapResultCode::EntryAlreadyExists,           "EntryAlreadyExists"           },
121
    { LdapResultCode::ObjectClassModsProhibited,    "ObjectClassModsProhibited"    },
122
    { LdapResultCode::AffectsMultipleDSAs,          "AffectsMultipleDSAs"          },
123
    { LdapResultCode::Other,                        "Other"                        }
124
    };
125
  // clang-format on
126
127
  // clang-format off
128
  static const std::unordered_map<uint8_t, LdapResultCode> UintToLdapResultCode{
129
    { static_cast<uint8_t>(LdapResultCode::Success),                   LdapResultCode::Success                   },
130
    { static_cast<uint8_t>(LdapResultCode::OperationsError),           LdapResultCode::OperationsError           },
131
    { static_cast<uint8_t>(LdapResultCode::ProtocolError),             LdapResultCode::ProtocolError             },
132
    { static_cast<uint8_t>(LdapResultCode::TimeLimitExceeded),         LdapResultCode::TimeLimitExceeded         },
133
    { static_cast<uint8_t>(LdapResultCode::SizeLimitExceeded),         LdapResultCode::SizeLimitExceeded         },
134
    { static_cast<uint8_t>(LdapResultCode::CompareFalse),              LdapResultCode::CompareFalse              },
135
    { static_cast<uint8_t>(LdapResultCode::CompareTrue),               LdapResultCode::CompareTrue               },
136
    { static_cast<uint8_t>(LdapResultCode::AuthMethodNotSupported),    LdapResultCode::AuthMethodNotSupported    },
137
    { static_cast<uint8_t>(LdapResultCode::StrongerAuthRequired),      LdapResultCode::StrongerAuthRequired      },
138
    { static_cast<uint8_t>(LdapResultCode::Referral),                  LdapResultCode::Referral                  },
139
    { static_cast<uint8_t>(LdapResultCode::AdminLimitExceeded),        LdapResultCode::AdminLimitExceeded        },
140
    { static_cast<uint8_t>(LdapResultCode::UnavailableCriticalExtension), LdapResultCode::UnavailableCriticalExtension },
141
    { static_cast<uint8_t>(LdapResultCode::ConfidentialityRequired),   LdapResultCode::ConfidentialityRequired   },
142
    { static_cast<uint8_t>(LdapResultCode::SaslBindInProgress),        LdapResultCode::SaslBindInProgress        },
143
    { static_cast<uint8_t>(LdapResultCode::NoSuchAttribute),           LdapResultCode::NoSuchAttribute           },
144
    { static_cast<uint8_t>(LdapResultCode::UndefinedAttributeType),    LdapResultCode::UndefinedAttributeType    },
145
    { static_cast<uint8_t>(LdapResultCode::InappropriateMatching),     LdapResultCode::InappropriateMatching     },
146
    { static_cast<uint8_t>(LdapResultCode::ConstraintViolation),       LdapResultCode::ConstraintViolation       },
147
    { static_cast<uint8_t>(LdapResultCode::AttributeOrValueExists),    LdapResultCode::AttributeOrValueExists    },
148
    { static_cast<uint8_t>(LdapResultCode::InvalidAttributeSyntax),    LdapResultCode::InvalidAttributeSyntax    },
149
    { static_cast<uint8_t>(LdapResultCode::NoSuchObject),              LdapResultCode::NoSuchObject              },
150
    { static_cast<uint8_t>(LdapResultCode::AliasProblem),              LdapResultCode::AliasProblem              },
151
    { static_cast<uint8_t>(LdapResultCode::InvalidDNSyntax),           LdapResultCode::InvalidDNSyntax           },
152
    { static_cast<uint8_t>(LdapResultCode::AliasDereferencingProblem), LdapResultCode::AliasDereferencingProblem },
153
    { static_cast<uint8_t>(LdapResultCode::InappropriateAuthentication),  LdapResultCode::InappropriateAuthentication },
154
    { static_cast<uint8_t>(LdapResultCode::InvalidCredentials),        LdapResultCode::InvalidCredentials        },
155
    { static_cast<uint8_t>(LdapResultCode::InsufficientAccessRights),  LdapResultCode::InsufficientAccessRights  },
156
    { static_cast<uint8_t>(LdapResultCode::Busy),                      LdapResultCode::Busy                      },
157
    { static_cast<uint8_t>(LdapResultCode::Unavailable),               LdapResultCode::Unavailable               },
158
    { static_cast<uint8_t>(LdapResultCode::UnwillingToPerform),        LdapResultCode::UnwillingToPerform        },
159
    { static_cast<uint8_t>(LdapResultCode::LoopDetect),                LdapResultCode::LoopDetect                },
160
    { static_cast<uint8_t>(LdapResultCode::NamingViolation),           LdapResultCode::NamingViolation           },
161
    { static_cast<uint8_t>(LdapResultCode::ObjectClassViolation),      LdapResultCode::ObjectClassViolation      },
162
    { static_cast<uint8_t>(LdapResultCode::NotAllowedOnNonLeaf),       LdapResultCode::NotAllowedOnNonLeaf       },
163
    { static_cast<uint8_t>(LdapResultCode::NotAllowedOnRDN),           LdapResultCode::NotAllowedOnRDN           },
164
    { static_cast<uint8_t>(LdapResultCode::EntryAlreadyExists),        LdapResultCode::EntryAlreadyExists        },
165
    { static_cast<uint8_t>(LdapResultCode::ObjectClassModsProhibited), LdapResultCode::ObjectClassModsProhibited },
166
    { static_cast<uint8_t>(LdapResultCode::AffectsMultipleDSAs),       LdapResultCode::AffectsMultipleDSAs       },
167
    { static_cast<uint8_t>(LdapResultCode::Other),                     LdapResultCode::Other                     }
168
  };
169
  // clang-format on
170
171
  std::string LdapResultCode::toString() const
172
4.23k
  {
173
4.23k
    return LdapResultCodeToString.at(m_Value);
174
4.23k
  }
175
176
  LdapResultCode LdapResultCode::fromUintValue(uint8_t value)
177
4.23k
  {
178
4.23k
    auto result = UintToLdapResultCode.find(value);
179
4.23k
    if (result != UintToLdapResultCode.end())
180
4.23k
    {
181
4.23k
      return result->second;
182
4.23k
    }
183
184
0
    return LdapResultCode::Unknown;
185
4.23k
  }
186
187
  // endregion
188
189
  // region LdapLayer
190
191
  LdapLayer::LdapLayer(uint16_t messageId, LdapOperationType operationType,
192
                       const std::vector<Asn1Record*>& messageRecords, const std::vector<LdapControl>& controls)
193
0
  {
194
0
    init(messageId, operationType, messageRecords, controls);
195
0
  }
196
197
  LdapLayer::LdapLayer(std::unique_ptr<Asn1Record> asn1Record, uint8_t* data, size_t dataLen, Layer* prevLayer,
198
                       Packet* packet)
199
51.3k
      : Layer(data, dataLen, prevLayer, packet, LDAP)
200
51.3k
  {
201
51.3k
    m_Asn1Record = std::move(asn1Record);
202
51.3k
  }
203
204
  void LdapLayer::init(uint16_t messageId, LdapOperationType operationType,
205
                       const std::vector<Asn1Record*>& messageRecords, const std::vector<LdapControl>& controls)
206
0
  {
207
0
    Asn1IntegerRecord messageIdRecord(messageId);
208
0
    std::unique_ptr<Asn1Record> messageRootRecord;
209
0
    if (!messageRecords.empty())
210
0
    {
211
0
      messageRootRecord =
212
0
          std::make_unique<Asn1ConstructedRecord>(Asn1TagClass::Application, operationType, messageRecords);
213
0
    }
214
0
    else
215
0
    {
216
0
      messageRootRecord =
217
0
          std::make_unique<Asn1GenericRecord>(Asn1TagClass::Application, false, operationType, "");
218
0
    }
219
220
0
    std::vector<Asn1Record*> rootSubRecords = { &messageIdRecord, messageRootRecord.get() };
221
222
0
    std::unique_ptr<Asn1ConstructedRecord> controlsRecord;
223
0
    if (!controls.empty())
224
0
    {
225
0
      PointerVector<Asn1Record> controlsSubRecords;
226
0
      for (const auto& control : controls)
227
0
      {
228
0
        Asn1OctetStringRecord controlTypeRecord(control.controlType);
229
0
        if (control.controlValue.empty())
230
0
        {
231
0
          controlsSubRecords.pushBack(new Asn1SequenceRecord({ &controlTypeRecord }));
232
0
        }
233
0
        else
234
0
        {
235
0
          auto controlValueSize = static_cast<size_t>(control.controlValue.size() / 2);
236
0
          std::unique_ptr<uint8_t[]> controlValue = std::make_unique<uint8_t[]>(controlValueSize);
237
0
          controlValueSize = hexStringToByteArray(control.controlValue, controlValue.get(), controlValueSize);
238
0
          Asn1OctetStringRecord controlValueRecord(controlValue.get(), controlValueSize);
239
0
          controlsSubRecords.pushBack(new Asn1SequenceRecord({ &controlTypeRecord, &controlValueRecord }));
240
0
        }
241
0
      }
242
0
      controlsRecord =
243
0
          std::make_unique<Asn1ConstructedRecord>(Asn1TagClass::ContextSpecific, 0, controlsSubRecords);
244
0
      rootSubRecords.push_back(controlsRecord.get());
245
0
    }
246
247
0
    Asn1SequenceRecord rootRecord(rootSubRecords);
248
249
0
    auto encodedData = rootRecord.encode();
250
0
    m_DataLen = encodedData.size();
251
0
    m_Data = new uint8_t[m_DataLen];
252
0
    std::copy(encodedData.begin(), encodedData.end(), m_Data);
253
0
    m_Protocol = LDAP;
254
0
    m_Asn1Record = Asn1Record::decode(m_Data, m_DataLen, true);
255
0
  }
256
257
  std::string LdapLayer::toString() const
258
16.0k
  {
259
16.0k
    auto extendedInfo = getExtendedInfoString();
260
16.0k
    return "LDAP Layer, " + getLdapOperationType().toString() + (extendedInfo.empty() ? "" : ", " + extendedInfo);
261
16.0k
  }
262
263
  LdapLayer* LdapLayer::parseLdapMessage(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet)
264
57.1k
  {
265
57.1k
    try
266
57.1k
    {
267
57.1k
      auto asn1Record = Asn1Record::decode(data, dataLen, true);
268
57.1k
      auto operationType = LdapOperationType::fromUintValue(
269
57.1k
          asn1Record->castAs<Asn1SequenceRecord>()->getSubRecords().at(operationTypeIndex)->getTagType());
270
57.1k
      switch (operationType)
271
57.1k
      {
272
3.49k
      case LdapOperationType::BindRequest:
273
3.49k
        return new LdapBindRequestLayer(std::move(asn1Record), data, dataLen, prevLayer, packet);
274
1.98k
      case LdapOperationType::BindResponse:
275
1.98k
        return new LdapBindResponseLayer(std::move(asn1Record), data, dataLen, prevLayer, packet);
276
4.25k
      case LdapOperationType::UnbindRequest:
277
4.25k
        return new LdapUnbindRequestLayer(std::move(asn1Record), data, dataLen, prevLayer, packet);
278
2.66k
      case LdapOperationType::SearchRequest:
279
2.66k
        return new LdapSearchRequestLayer(std::move(asn1Record), data, dataLen, prevLayer, packet);
280
839
      case LdapOperationType::SearchResultEntry:
281
839
        return new LdapSearchResultEntryLayer(std::move(asn1Record), data, dataLen, prevLayer, packet);
282
7.23k
      case LdapOperationType::SearchResultDone:
283
7.23k
        return new LdapSearchResultDoneLayer(std::move(asn1Record), data, dataLen, prevLayer, packet);
284
955
      case LdapOperationType::ModifyResponse:
285
955
        return new LdapModifyResponseLayer(std::move(asn1Record), data, dataLen, prevLayer, packet);
286
30
      case LdapOperationType::AddResponse:
287
30
        return new LdapAddResponseLayer(std::move(asn1Record), data, dataLen, prevLayer, packet);
288
80
      case LdapOperationType::DeleteResponse:
289
80
        return new LdapDeleteResponseLayer(std::move(asn1Record), data, dataLen, prevLayer, packet);
290
1.79k
      case LdapOperationType::ModifyDNResponse:
291
1.79k
        return new LdapModifyDNResponseLayer(std::move(asn1Record), data, dataLen, prevLayer, packet);
292
1.04k
      case LdapOperationType::CompareResponse:
293
1.04k
        return new LdapCompareResponseLayer(std::move(asn1Record), data, dataLen, prevLayer, packet);
294
340
      case LdapOperationType::Unknown:
295
340
        return nullptr;
296
27.0k
      default:
297
27.0k
        return new LdapLayer(std::move(asn1Record), data, dataLen, prevLayer, packet);
298
57.1k
      }
299
57.1k
    }
300
57.1k
    catch (...)
301
57.1k
    {
302
5.37k
      return nullptr;
303
5.37k
    }
304
57.1k
  }
305
306
  Asn1SequenceRecord* LdapLayer::getRootAsn1Record() const
307
23.5k
  {
308
23.5k
    return m_Asn1Record->castAs<Asn1SequenceRecord>();
309
23.5k
  }
310
311
  Asn1ConstructedRecord* LdapLayer::getLdapOperationAsn1Record() const
312
23.5k
  {
313
23.5k
    return getRootAsn1Record()->getSubRecords().at(operationTypeIndex)->castAs<Asn1ConstructedRecord>();
314
23.5k
  }
315
316
  uint16_t LdapLayer::getMessageID() const
317
0
  {
318
0
    return getRootAsn1Record()
319
0
        ->getSubRecords()
320
0
        .at(messageIdIndex)
321
0
        ->castAs<Asn1IntegerRecord>()
322
0
        ->getIntValue<uint16_t>();
323
0
  }
324
325
  std::vector<LdapControl> LdapLayer::getControls() const
326
0
  {
327
0
    std::vector<LdapControl> controls;
328
0
    if (getRootAsn1Record()->getSubRecords().size() <= controlsIndex)
329
0
    {
330
0
      return controls;
331
0
    }
332
333
0
    auto controlsRecord = getRootAsn1Record()->getSubRecords().at(controlsIndex)->castAs<Asn1ConstructedRecord>();
334
0
    for (auto controlRecord : controlsRecord->getSubRecords())
335
0
    {
336
0
      auto controlSequence = controlRecord->castAs<Asn1SequenceRecord>();
337
0
      auto controlType =
338
0
          controlSequence->getSubRecords().at(controlTypeIndex)->castAs<Asn1OctetStringRecord>()->getValue();
339
0
      std::string controlValue;
340
0
      if (controlSequence->getSubRecords().size() > controlValueIndex)
341
0
      {
342
0
        controlValue =
343
0
            controlSequence->getSubRecords().at(controlValueIndex)->castAs<Asn1OctetStringRecord>()->getValue();
344
0
      }
345
0
      controls.push_back({ controlType, controlValue });
346
0
    }
347
348
0
    return controls;
349
0
  }
350
351
  LdapOperationType LdapLayer::getLdapOperationType() const
352
14.3k
  {
353
14.3k
    uint8_t tagType;
354
14.3k
    try
355
14.3k
    {
356
14.3k
      tagType = getLdapOperationAsn1Record()->getTagType();
357
14.3k
    }
358
14.3k
    catch (...)
359
14.3k
    {
360
128
      tagType = LdapOperationType::Unknown;
361
128
    }
362
363
14.3k
    return LdapOperationType::fromUintValue(tagType);
364
14.3k
  }
365
366
  void LdapLayer::parseNextLayer()
367
51.3k
  {
368
51.3k
    size_t headerLen = getHeaderLen();
369
51.3k
    if (m_DataLen <= headerLen || headerLen == 0)
370
25.9k
      return;
371
372
25.4k
    uint8_t* payload = m_Data + headerLen;
373
25.4k
    size_t payloadLen = m_DataLen - headerLen;
374
375
25.4k
    m_NextLayer = LdapLayer::parseLdapMessage(payload, payloadLen, this, m_Packet);
376
25.4k
  }
377
  // endregion
378
379
  // region LdapResponseLayer
380
381
  LdapResponseLayer::LdapResponseLayer(uint16_t messageId, LdapOperationType operationType, LdapResultCode resultCode,
382
                                       const std::string& matchedDN, const std::string& diagnosticMessage,
383
                                       const std::vector<std::string>& referral,
384
                                       const std::vector<LdapControl>& controls)
385
0
  {
386
0
    LdapResponseLayer::init(messageId, operationType, resultCode, matchedDN, diagnosticMessage, referral, {},
387
0
                            controls);
388
0
  }
389
390
  void LdapResponseLayer::init(uint16_t messageId, LdapOperationType operationType, LdapResultCode resultCode,
391
                               const std::string& matchedDN, const std::string& diagnosticMessage,
392
                               const std::vector<std::string>& referral,
393
                               const std::vector<Asn1Record*>& additionalRecords,
394
                               const std::vector<LdapControl>& controls)
395
0
  {
396
0
    Asn1EnumeratedRecord resultCodeRecord(resultCode);
397
0
    Asn1OctetStringRecord matchedDNRecord(matchedDN);
398
0
    Asn1OctetStringRecord diagnosticMessageRecord(diagnosticMessage);
399
400
0
    std::vector<Asn1Record*> messageRecords = { &resultCodeRecord, &matchedDNRecord, &diagnosticMessageRecord };
401
402
0
    std::unique_ptr<Asn1ConstructedRecord> referralRecord;
403
0
    if (!referral.empty())
404
0
    {
405
0
      PointerVector<Asn1Record> referralSubRecords;
406
0
      for (const auto& uri : referral)
407
0
      {
408
0
        referralSubRecords.pushBack(new Asn1OctetStringRecord(uri));
409
0
      }
410
0
      referralRecord = std::make_unique<Asn1ConstructedRecord>(Asn1TagClass::ContextSpecific, referralTagType,
411
0
                                                               referralSubRecords);
412
0
      messageRecords.push_back(referralRecord.get());
413
0
    }
414
415
0
    if (!additionalRecords.empty())
416
0
    {
417
0
      for (auto additionalRecord : additionalRecords)
418
0
      {
419
0
        messageRecords.push_back(additionalRecord);
420
0
      }
421
0
    }
422
423
0
    LdapLayer::init(messageId, operationType, messageRecords, controls);
424
0
  }
425
426
  LdapResultCode LdapResponseLayer::getResultCode() const
427
4.23k
  {
428
4.23k
    return LdapResultCode::fromUintValue(getLdapOperationAsn1Record()
429
4.23k
                                             ->getSubRecords()
430
4.23k
                                             .at(resultCodeIndex)
431
4.23k
                                             ->castAs<Asn1EnumeratedRecord>()
432
4.23k
                                             ->getIntValue<uint8_t>());
433
4.23k
  }
434
435
  std::string LdapResponseLayer::getMatchedDN() const
436
0
  {
437
0
    return getLdapOperationAsn1Record()
438
0
        ->getSubRecords()
439
0
        .at(matchedDNIndex)
440
0
        ->castAs<Asn1OctetStringRecord>()
441
0
        ->getValue();
442
0
  }
443
444
  std::string LdapResponseLayer::getDiagnosticMessage() const
445
0
  {
446
0
    return getLdapOperationAsn1Record()
447
0
        ->getSubRecords()
448
0
        .at(diagnotsticsMessageIndex)
449
0
        ->castAs<Asn1OctetStringRecord>()
450
0
        ->getValue();
451
0
  }
452
453
  std::vector<std::string> LdapResponseLayer::getReferral() const
454
0
  {
455
0
    std::vector<std::string> result;
456
0
    if (getLdapOperationAsn1Record()->getSubRecords().size() <= referralIndex)
457
0
    {
458
0
      return result;
459
0
    }
460
461
0
    auto referralRecord = getLdapOperationAsn1Record()->getSubRecords().at(referralIndex);
462
0
    if (referralRecord->getTagClass() != Asn1TagClass::ContextSpecific ||
463
0
        referralRecord->getTagType() != referralTagType)
464
0
    {
465
0
      return result;
466
0
    }
467
468
0
    for (auto uriRecord : referralRecord->castAs<Asn1ConstructedRecord>()->getSubRecords())
469
0
    {
470
0
      result.push_back(uriRecord->castAs<Asn1OctetStringRecord>()->getValue());
471
0
    }
472
473
0
    return result;
474
0
  }
475
476
  std::string LdapResponseLayer::getExtendedInfoString() const
477
4.23k
  {
478
4.23k
    return getResultCode().toString();
479
4.23k
  }
480
  // endregion
481
482
  // region LdapBindRequestLayer
483
484
  LdapBindRequestLayer::LdapBindRequestLayer(uint16_t messageId, uint8_t version, const std::string& name,
485
                                             const std::string& simpleAuthentication,
486
                                             const std::vector<LdapControl>& controls)
487
0
  {
488
0
    Asn1IntegerRecord versionRecord(version);
489
0
    Asn1OctetStringRecord nameRecord(name);
490
0
    std::vector<Asn1Record*> messageRecords = { &versionRecord, &nameRecord };
491
0
    std::unique_ptr<Asn1GenericRecord> simpleAuthenticationRecord;
492
0
    if (!simpleAuthentication.empty())
493
0
    {
494
0
      auto data = reinterpret_cast<const uint8_t*>(simpleAuthentication.data());
495
0
      simpleAuthenticationRecord = std::make_unique<Asn1GenericRecord>(
496
0
          Asn1TagClass::ContextSpecific, false,
497
0
          static_cast<uint8_t>(LdapBindRequestLayer::AuthenticationType::Simple), data,
498
0
          simpleAuthentication.size());
499
0
      messageRecords.push_back(simpleAuthenticationRecord.get());
500
0
    }
501
502
0
    LdapLayer::init(messageId, LdapOperationType::BindRequest, messageRecords, controls);
503
0
  }
504
505
  LdapBindRequestLayer::LdapBindRequestLayer(uint16_t messageId, uint8_t version, const std::string& name,
506
                                             const SaslAuthentication& saslAuthentication,
507
                                             const std::vector<LdapControl>& controls)
508
0
  {
509
0
    Asn1IntegerRecord versionRecord(version);
510
0
    Asn1OctetStringRecord nameRecord(name);
511
0
    std::vector<Asn1Record*> messageRecords = { &versionRecord, &nameRecord };
512
0
    std::unique_ptr<Asn1ConstructedRecord> saslAuthenticationRecord;
513
0
    if (!saslAuthentication.mechanism.empty())
514
0
    {
515
0
      PointerVector<Asn1Record> saslAuthenticationRecords;
516
0
      saslAuthenticationRecords.pushBack(new Asn1OctetStringRecord(saslAuthentication.mechanism));
517
0
      if (!saslAuthentication.credentials.empty())
518
0
      {
519
0
        auto credentialsRecord = new Asn1OctetStringRecord(saslAuthentication.credentials.data(),
520
0
                                                           saslAuthentication.credentials.size());
521
0
        saslAuthenticationRecords.pushBack(credentialsRecord);
522
0
      }
523
524
0
      saslAuthenticationRecord = std::make_unique<Asn1ConstructedRecord>(
525
0
          Asn1TagClass::ContextSpecific, static_cast<uint8_t>(LdapBindRequestLayer::AuthenticationType::Sasl),
526
0
          saslAuthenticationRecords);
527
0
      messageRecords.push_back(saslAuthenticationRecord.get());
528
0
    }
529
530
0
    LdapLayer::init(messageId, LdapOperationType::BindRequest, messageRecords, controls);
531
0
  }
532
533
  uint32_t LdapBindRequestLayer::getVersion() const
534
0
  {
535
0
    return getLdapOperationAsn1Record()
536
0
        ->getSubRecords()
537
0
        .at(versionIndex)
538
0
        ->castAs<Asn1IntegerRecord>()
539
0
        ->getIntValue<uint32_t>();
540
0
  }
541
542
  std::string LdapBindRequestLayer::getName() const
543
0
  {
544
0
    return getLdapOperationAsn1Record()->getSubRecords().at(nameIndex)->castAs<Asn1OctetStringRecord>()->getValue();
545
0
  }
546
547
  LdapBindRequestLayer::AuthenticationType LdapBindRequestLayer::getAuthenticationType() const
548
1.39k
  {
549
1.39k
    if (getLdapOperationAsn1Record()->getSubRecords().size() <= credentialIndex)
550
24
    {
551
24
      return LdapBindRequestLayer::AuthenticationType::NotApplicable;
552
24
    }
553
554
1.37k
    auto authType = getLdapOperationAsn1Record()->getSubRecords().at(credentialIndex)->getTagType();
555
1.37k
    switch (authType)
556
1.37k
    {
557
1.19k
    case 0:
558
1.19k
      return LdapBindRequestLayer::AuthenticationType::Simple;
559
160
    case 3:
560
160
      return LdapBindRequestLayer::AuthenticationType::Sasl;
561
16
    default:
562
16
      return LdapBindRequestLayer::AuthenticationType::NotApplicable;
563
1.37k
    }
564
1.37k
  }
565
566
  std::string LdapBindRequestLayer::getSimpleAuthentication() const
567
0
  {
568
0
    if (getAuthenticationType() != LdapBindRequestLayer::AuthenticationType::Simple)
569
0
    {
570
0
      throw std::invalid_argument("Authentication type is not simple");
571
0
    }
572
573
0
    auto authRecord =
574
0
        getLdapOperationAsn1Record()->getSubRecords().at(credentialIndex)->castAs<Asn1GenericRecord>();
575
0
    return { reinterpret_cast<const char*>(authRecord->getValue()), authRecord->getValueLength() };
576
0
  }
577
578
  LdapBindRequestLayer::SaslAuthentication LdapBindRequestLayer::getSaslAuthentication() const
579
0
  {
580
0
    if (getAuthenticationType() != LdapBindRequestLayer::AuthenticationType::Sasl)
581
0
    {
582
0
      throw std::invalid_argument("Authentication type is not sasl");
583
0
    }
584
585
0
    auto authRecord =
586
0
        getLdapOperationAsn1Record()->getSubRecords().at(credentialIndex)->castAs<Asn1ConstructedRecord>();
587
0
    std::string mechanism;
588
0
    std::vector<uint8_t> credentials;
589
0
    if (authRecord->getSubRecords().size() > saslMechanismIndex)
590
0
    {
591
0
      mechanism = authRecord->getSubRecords().at(saslMechanismIndex)->castAs<Asn1OctetStringRecord>()->getValue();
592
0
    }
593
0
    if (authRecord->getSubRecords().size() > saslCredentialsIndex)
594
0
    {
595
0
      auto credentialsAsString =
596
0
          authRecord->getSubRecords().at(saslCredentialsIndex)->castAs<Asn1OctetStringRecord>()->getValue();
597
0
      credentials.resize(credentialsAsString.size() / 2);
598
0
      hexStringToByteArray(credentialsAsString, credentials.data(), credentials.size());
599
0
    }
600
601
0
    return { mechanism, credentials };
602
0
  }
603
604
  std::string LdapBindRequestLayer::getExtendedInfoString() const
605
1.39k
  {
606
1.39k
    switch (getAuthenticationType())
607
1.39k
    {
608
1.19k
    case AuthenticationType::Simple:
609
1.19k
      return "simple";
610
160
    case AuthenticationType::Sasl:
611
160
      return "sasl";
612
40
    default:
613
40
      return "Unknown";
614
1.39k
    }
615
1.39k
  }
616
617
  // endregion
618
619
  // region LdapBindResponseLayer
620
621
  LdapBindResponseLayer::LdapBindResponseLayer(uint16_t messageId, LdapResultCode resultCode,
622
                                               const std::string& matchedDN, const std::string& diagnosticMessage,
623
                                               const std::vector<std::string>& referral,
624
                                               const std::vector<uint8_t>& serverSaslCredentials,
625
                                               const std::vector<LdapControl>& controls)
626
0
  {
627
0
    std::vector<Asn1Record*> additionalRecords;
628
0
    std::unique_ptr<Asn1Record> serverSaslCredentialsRecord;
629
0
    if (!serverSaslCredentials.empty())
630
0
    {
631
0
      serverSaslCredentialsRecord =
632
0
          std::make_unique<Asn1GenericRecord>(Asn1TagClass::ContextSpecific, false, serverSaslCredentialsTagType,
633
0
                                              serverSaslCredentials.data(), serverSaslCredentials.size());
634
0
      additionalRecords.push_back(serverSaslCredentialsRecord.get());
635
0
    }
636
637
0
    LdapResponseLayer::init(messageId, LdapOperationType::BindResponse, resultCode, matchedDN, diagnosticMessage,
638
0
                            referral, additionalRecords, controls);
639
0
  }
640
641
  std::vector<uint8_t> LdapBindResponseLayer::getServerSaslCredentials() const
642
0
  {
643
0
    try
644
0
    {
645
0
      auto serverSaslCredentialsRecord =
646
0
          getLdapOperationAsn1Record()->getSubRecords().back()->castAs<Asn1GenericRecord>();
647
0
      return { serverSaslCredentialsRecord->getValue(),
648
0
             serverSaslCredentialsRecord->getValue() + serverSaslCredentialsRecord->getValueLength() };
649
0
    }
650
0
    catch (const std::exception&)
651
0
    {
652
0
      return {};
653
0
    }
654
0
  }
655
656
  // endregion
657
658
  // region LdapUnbindRequestLayer
659
660
  LdapUnbindRequestLayer::LdapUnbindRequestLayer(uint16_t messageId, const std::vector<LdapControl>& controls)
661
0
  {
662
0
    LdapLayer::init(messageId, LdapOperationType::UnbindRequest, {}, controls);
663
0
  }
664
665
  // endregion
666
667
  // region LdapSearchRequestLayer
668
669
  const std::unordered_map<LdapSearchRequestLayer::SearchRequestScope::Value, std::string,
670
                           EnumClassHash<LdapSearchRequestLayer::SearchRequestScope::Value>>
671
      SearchRequestScopeToString{
672
        { LdapSearchRequestLayer::SearchRequestScope::BaseObject,   "BaseObject"   },
673
        { LdapSearchRequestLayer::SearchRequestScope::SingleLevel,  "SingleLevel"  },
674
        { LdapSearchRequestLayer::SearchRequestScope::WholeSubtree, "WholeSubtree" },
675
        { LdapSearchRequestLayer::SearchRequestScope::Unknown,      "Unknown"      }
676
    };
677
678
  const std::unordered_map<LdapSearchRequestLayer::DerefAliases::Value, std::string,
679
                           EnumClassHash<LdapSearchRequestLayer::DerefAliases::Value>>
680
      DerefAliasesToString{
681
        { LdapSearchRequestLayer::DerefAliases::NeverDerefAliases,   "NeverDerefAliases"   },
682
        { LdapSearchRequestLayer::DerefAliases::DerefInSearching,    "DerefInSearching"    },
683
        { LdapSearchRequestLayer::DerefAliases::DerefFindingBaseObj, "DerefFindingBaseObj" },
684
        { LdapSearchRequestLayer::DerefAliases::DerefAlways,         "DerefAlways"         },
685
        { LdapSearchRequestLayer::DerefAliases::Unknown,             "Unknown"             }
686
    };
687
688
  std::string LdapSearchRequestLayer::SearchRequestScope::toString() const
689
1.06k
  {
690
1.06k
    return SearchRequestScopeToString.at(m_Value);
691
1.06k
  }
692
693
  LdapSearchRequestLayer::SearchRequestScope LdapSearchRequestLayer::SearchRequestScope::fromUintValue(uint8_t value)
694
1.06k
  {
695
1.06k
    if (value <= 2)
696
982
    {
697
982
      return static_cast<LdapSearchRequestLayer::SearchRequestScope::Value>(value);
698
982
    }
699
700
82
    return LdapSearchRequestLayer::SearchRequestScope::Unknown;
701
1.06k
  }
702
703
  std::string LdapSearchRequestLayer::DerefAliases::toString() const
704
0
  {
705
0
    return DerefAliasesToString.at(m_Value);
706
0
  }
707
708
  LdapSearchRequestLayer::DerefAliases LdapSearchRequestLayer::DerefAliases::fromUintValue(uint8_t value)
709
0
  {
710
0
    if (value <= 3)
711
0
    {
712
0
      return static_cast<LdapSearchRequestLayer::DerefAliases::Value>(value);
713
0
    }
714
715
0
    return LdapSearchRequestLayer::DerefAliases::Unknown;
716
0
  }
717
718
  LdapSearchRequestLayer::LdapSearchRequestLayer(uint16_t messageId, const std::string& baseObject,
719
                                                 SearchRequestScope scope, DerefAliases derefAliases,
720
                                                 uint8_t sizeLimit, uint8_t timeLimit, bool typesOnly,
721
                                                 Asn1Record* filterRecord, const std::vector<std::string>& attributes,
722
                                                 const std::vector<LdapControl>& controls)
723
0
  {
724
0
    Asn1OctetStringRecord baseObjectRecord(baseObject);
725
0
    Asn1EnumeratedRecord scopeRecord(scope);
726
0
    Asn1EnumeratedRecord derefAliasesRecord(derefAliases);
727
0
    Asn1IntegerRecord sizeLimitRecord(sizeLimit);
728
0
    Asn1IntegerRecord timeLimitRecord(timeLimit);
729
0
    Asn1BooleanRecord typeOnlyRecord(typesOnly);
730
731
0
    PointerVector<Asn1Record> attributeSubRecords;
732
0
    for (const auto& attribute : attributes)
733
0
    {
734
0
      attributeSubRecords.pushBack(new Asn1OctetStringRecord(attribute));
735
0
    }
736
0
    Asn1SequenceRecord attributesRecord(attributeSubRecords);
737
738
0
    LdapLayer::init(messageId, LdapOperationType::SearchRequest,
739
0
                    { &baseObjectRecord, &scopeRecord, &derefAliasesRecord, &sizeLimitRecord, &timeLimitRecord,
740
0
                      &typeOnlyRecord, filterRecord, &attributesRecord },
741
0
                    controls);
742
0
  }
743
744
  std::string LdapSearchRequestLayer::getBaseObject() const
745
1.06k
  {
746
1.06k
    return getLdapOperationAsn1Record()
747
1.06k
        ->getSubRecords()
748
1.06k
        .at(baseObjectIndex)
749
1.06k
        ->castAs<Asn1OctetStringRecord>()
750
1.06k
        ->getValue();
751
1.06k
  }
752
753
  LdapSearchRequestLayer::SearchRequestScope LdapSearchRequestLayer::getScope() const
754
1.06k
  {
755
1.06k
    return LdapSearchRequestLayer::SearchRequestScope::fromUintValue(getLdapOperationAsn1Record()
756
1.06k
                                                                         ->getSubRecords()
757
1.06k
                                                                         .at(scopeIndex)
758
1.06k
                                                                         ->castAs<Asn1EnumeratedRecord>()
759
1.06k
                                                                         ->getIntValue<uint8_t>());
760
1.06k
  }
761
762
  LdapSearchRequestLayer::DerefAliases LdapSearchRequestLayer::getDerefAlias() const
763
0
  {
764
0
    return LdapSearchRequestLayer::DerefAliases::fromUintValue(getLdapOperationAsn1Record()
765
0
                                                                   ->getSubRecords()
766
0
                                                                   .at(derefAliasIndex)
767
0
                                                                   ->castAs<Asn1EnumeratedRecord>()
768
0
                                                                   ->getIntValue<uint8_t>());
769
0
  }
770
771
  uint8_t LdapSearchRequestLayer::getSizeLimit() const
772
0
  {
773
0
    return getLdapOperationAsn1Record()
774
0
        ->getSubRecords()
775
0
        .at(sizeLimitIndex)
776
0
        ->castAs<Asn1IntegerRecord>()
777
0
        ->getIntValue<uint8_t>();
778
0
  }
779
780
  uint8_t LdapSearchRequestLayer::getTimeLimit() const
781
0
  {
782
0
    return getLdapOperationAsn1Record()
783
0
        ->getSubRecords()
784
0
        .at(timeLimitIndex)
785
0
        ->castAs<Asn1IntegerRecord>()
786
0
        ->getIntValue<uint8_t>();
787
0
  }
788
789
  bool LdapSearchRequestLayer::getTypesOnly() const
790
0
  {
791
0
    return getLdapOperationAsn1Record()
792
0
        ->getSubRecords()
793
0
        .at(typesOnlyIndex)
794
0
        ->castAs<Asn1BooleanRecord>()
795
0
        ->getValue();
796
0
  }
797
798
  Asn1Record* LdapSearchRequestLayer::getFilter() const
799
0
  {
800
0
    return getLdapOperationAsn1Record()->getSubRecords().at(filterIndex);
801
0
  }
802
803
  std::vector<std::string> LdapSearchRequestLayer::getAttributes() const
804
0
  {
805
0
    std::vector<std::string> result;
806
0
    if (getLdapOperationAsn1Record()->getSubRecords().size() <= attributesIndex)
807
0
    {
808
0
      return result;
809
0
    }
810
811
0
    auto attributesRecord =
812
0
        getLdapOperationAsn1Record()->getSubRecords().at(attributesIndex)->castAs<Asn1SequenceRecord>();
813
0
    for (auto attribute : attributesRecord->getSubRecords())
814
0
    {
815
0
      result.push_back(attribute->castAs<Asn1OctetStringRecord>()->getValue());
816
0
    }
817
818
0
    return result;
819
0
  }
820
821
  std::string LdapSearchRequestLayer::getExtendedInfoString() const
822
1.06k
  {
823
1.06k
    auto baseObject = getBaseObject();
824
1.06k
    if (baseObject.empty())
825
0
    {
826
0
      baseObject = "ROOT";
827
0
    }
828
829
1.06k
    return "\"" + baseObject + "\", " + getScope().toString();
830
1.06k
  }
831
832
  // endregion
833
834
  // region LdapSearchResultEntryLayer
835
836
  LdapSearchResultEntryLayer::LdapSearchResultEntryLayer(uint16_t messageId, const std::string& objectName,
837
                                                         const std::vector<LdapAttribute>& attributes,
838
                                                         const std::vector<LdapControl>& controls)
839
0
  {
840
0
    PointerVector<Asn1Record> attributesSubRecords;
841
0
    for (const auto& attribute : attributes)
842
0
    {
843
0
      PointerVector<Asn1Record> valuesSubRecords;
844
0
      for (const auto& value : attribute.values)
845
0
      {
846
0
        valuesSubRecords.pushBack(new Asn1OctetStringRecord(value));
847
0
      }
848
849
0
      Asn1OctetStringRecord typeRecord(attribute.type);
850
0
      Asn1SetRecord valuesRecord(valuesSubRecords);
851
852
0
      attributesSubRecords.pushBack(new Asn1SequenceRecord({ &typeRecord, &valuesRecord }));
853
0
    }
854
855
0
    Asn1OctetStringRecord objectNameRecord(objectName);
856
0
    Asn1SequenceRecord attributesRecord(attributesSubRecords);
857
858
0
    LdapLayer::init(messageId, LdapOperationType::SearchResultEntry, { &objectNameRecord, &attributesRecord },
859
0
                    controls);
860
0
  }
861
862
  std::string LdapSearchResultEntryLayer::getObjectName() const
863
0
  {
864
0
    return getLdapOperationAsn1Record()
865
0
        ->getSubRecords()
866
0
        .at(objectNameIndex)
867
0
        ->castAs<Asn1OctetStringRecord>()
868
0
        ->getValue();
869
0
  }
870
871
  std::vector<LdapAttribute> LdapSearchResultEntryLayer::getAttributes() const
872
0
  {
873
0
    std::vector<LdapAttribute> result;
874
875
0
    auto attributes =
876
0
        getLdapOperationAsn1Record()->getSubRecords().at(attributesIndex)->castAs<Asn1SequenceRecord>();
877
0
    for (auto attributeRecord : attributes->getSubRecords())
878
0
    {
879
0
      auto attrAsSequence = attributeRecord->castAs<Asn1SequenceRecord>();
880
881
0
      auto type =
882
0
          attrAsSequence->getSubRecords().at(attributeTypeIndex)->castAs<Asn1OctetStringRecord>()->getValue();
883
884
0
      std::vector<std::string> values;
885
0
      auto valuesRecord = attrAsSequence->getSubRecords().at(attributeValueIndex)->castAs<Asn1SetRecord>();
886
887
0
      for (auto valueRecord : valuesRecord->getSubRecords())
888
0
      {
889
0
        values.push_back(valueRecord->castAs<Asn1OctetStringRecord>()->getValue());
890
0
      }
891
892
0
      LdapAttribute ldapAttribute = { type, values };
893
0
      result.push_back(ldapAttribute);
894
0
    }
895
896
0
    return result;
897
0
  }
898
899
  // endregion
900
}  // namespace pcpp