Skip to content

Commit

Permalink
Add string representation for ASN.1 records (#1403)
Browse files Browse the repository at this point in the history
* Add ASN.1 string representation

* Address PR comments

* Fix hash + add comment

* Another attempt to fix hash
  • Loading branch information
seladb authored May 26, 2024
1 parent 4dd1a9f commit 2890a2f
Show file tree
Hide file tree
Showing 3 changed files with 290 additions and 11 deletions.
35 changes: 30 additions & 5 deletions Packet++/header/Asn1Codec.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ namespace pcpp
enum class Asn1TagClass : uint8_t
{
/** The Universal tag class */
Universal,
Universal = 0,
/** The Application tag class */
Application,
Application = 1,
/** The Context-Specific tag class */
ContextSpecific,
ContextSpecific = 2,
/** The Private tag class */
Private
Private = 3,
};

/**
Expand Down Expand Up @@ -169,6 +169,11 @@ namespace pcpp
*/
size_t getTotalLength() const { return m_TotalLength; }

/**
* @return A string representation of the record
*/
std::string toString();

/**
* A templated method that accepts a class derived from Asn1Record as its template argument and attempts
* to cast the current instance to that type
Expand Down Expand Up @@ -211,6 +216,10 @@ namespace pcpp

uint8_t encodeTag();
std::vector<uint8_t> encodeLength() const;

virtual std::vector<std::string> toStringList();

friend class Asn1ConstructedRecord;
};

/**
Expand Down Expand Up @@ -280,6 +289,8 @@ namespace pcpp
void decodeValue(uint8_t* data, bool lazy) override;
std::vector<uint8_t> encodeValue() const override;

std::vector<std::string> toStringList() override;

private:
PointerVector<Asn1Record> m_SubRecords;
};
Expand Down Expand Up @@ -362,6 +373,8 @@ namespace pcpp
void decodeValue(uint8_t* data, bool lazy) override;
std::vector<uint8_t> encodeValue() const override;

std::vector<std::string> toStringList() override;

private:
uint32_t m_Value = 0;
};
Expand Down Expand Up @@ -395,11 +408,18 @@ namespace pcpp

public:
/**
* A constructor to create a record of type Octet String
* A constructor to create a record of type Octet String from a printable value
* @param value A string to set as the record value
*/
explicit Asn1OctetStringRecord(const std::string& value);

/**
* A constructor to create a record of type Octet String from a non-printable value
* @param value A byte array to set as the record value
* @param valueLength The length of the byte array
*/
explicit Asn1OctetStringRecord(const uint8_t* value, size_t valueLength);

/**
* @return The string value of this record
*/
Expand All @@ -409,8 +429,11 @@ namespace pcpp
void decodeValue(uint8_t* data, bool lazy) override;
std::vector<uint8_t> encodeValue() const override;

std::vector<std::string> toStringList() override;

private:
std::string m_Value;
bool m_IsPrintable = true;

Asn1OctetStringRecord() = default;
};
Expand Down Expand Up @@ -439,6 +462,8 @@ namespace pcpp
void decodeValue(uint8_t* data, bool lazy) override;
std::vector<uint8_t> encodeValue() const override;

std::vector<std::string> toStringList() override;

private:
Asn1BooleanRecord() = default;

Expand Down
189 changes: 185 additions & 4 deletions Packet++/src/Asn1Codec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
#include "Asn1Codec.h"
#include "GeneralUtils.h"
#include "EndianPortable.h"
#include <unordered_map>
#include <numeric>
#include <algorithm>
#include <iostream>
#include <sstream>
#include <cstring>
#include <cmath>
#include <limits>
Expand All @@ -12,7 +16,85 @@
#undef max
#endif

namespace pcpp {
namespace pcpp
{
template<typename EnumClass>
struct EnumClassHash
{
size_t operator()(const EnumClass& value) const
{
return static_cast<int>(value);
}
};

const std::unordered_map<Asn1TagClass, std::string, EnumClassHash<Asn1TagClass>> Asn1TagClassToString {
{Asn1TagClass::Universal, "Universal" },
{Asn1TagClass::ContextSpecific, "ContextSpecific" },
{Asn1TagClass::Application, "Application"},
{Asn1TagClass::Private, "Private"}
};

std::string toString(Asn1TagClass tagClass)
{
if (Asn1TagClassToString.find(tagClass) != Asn1TagClassToString.end())
{
return Asn1TagClassToString.at(tagClass);
}

return "Unknown";
}

const std::unordered_map<Asn1UniversalTagType, std::string, EnumClassHash<Asn1UniversalTagType>> Asn1UniversalTagTypeToString {
{Asn1UniversalTagType::EndOfContent, "EndOfContent"},
{Asn1UniversalTagType::Boolean, "Boolean"},
{Asn1UniversalTagType::Integer, "Integer"},
{Asn1UniversalTagType::BitString, "BitString"},
{Asn1UniversalTagType::OctetString, "OctetString"},
{Asn1UniversalTagType::Null, "Null"},
{Asn1UniversalTagType::ObjectIdentifier, "ObjectIdentifier"},
{Asn1UniversalTagType::ObjectDescriptor, "ObjectDescriptor"},
{Asn1UniversalTagType::External, "External"},
{Asn1UniversalTagType::Real, "Real"},
{Asn1UniversalTagType::Enumerated, "Enumerated"},
{Asn1UniversalTagType::EmbeddedPDV, "EmbeddedPDV"},
{Asn1UniversalTagType::UTF8String, "UTF8String"},
{Asn1UniversalTagType::RelativeObjectIdentifier, "RelativeObjectIdentifier"},
{Asn1UniversalTagType::Time, "Time"},
{Asn1UniversalTagType::Reserved, "Reserved"},
{Asn1UniversalTagType::Sequence, "Sequence"},
{Asn1UniversalTagType::Set, "Set"},
{Asn1UniversalTagType::NumericString, "NumericString"},
{Asn1UniversalTagType::PrintableString, "PrintableString"},
{Asn1UniversalTagType::T61String, "T61String"},
{Asn1UniversalTagType::VideotexString, "VideotexString"},
{Asn1UniversalTagType::IA5String, "IA5String"},
{Asn1UniversalTagType::UTCTime, "UTCTime"},
{Asn1UniversalTagType::GeneralizedTime, "GeneralizedTime"},
{Asn1UniversalTagType::GraphicString, "GraphicString"},
{Asn1UniversalTagType::VisibleString, "VisibleString"},
{Asn1UniversalTagType::GeneralString, "GeneralString"},
{Asn1UniversalTagType::UniversalString, "UniversalString"},
{Asn1UniversalTagType::CharacterString, "CharacterString"},
{Asn1UniversalTagType::BMPString, "BMPString"},
{Asn1UniversalTagType::Date, "Date"},
{Asn1UniversalTagType::TimeOfDay, "TimeOfDay"},
{Asn1UniversalTagType::DateTime, "DateTime"},
{Asn1UniversalTagType::Duration, "Duration"},
{Asn1UniversalTagType::ObjectIdentifierIRI, "ObjectIdentifierIRI"},
{Asn1UniversalTagType::RelativeObjectIdentifierIRI, "RelativeObjectIdentifierIRI"},
{Asn1UniversalTagType::NotApplicable, "Unknown"}
};

std::string toString(Asn1UniversalTagType tagType)
{
if (Asn1UniversalTagTypeToString.find(tagType) != Asn1UniversalTagTypeToString.end())
{
return Asn1UniversalTagTypeToString.at(tagType);
}

return "Unknown";
}

std::unique_ptr<Asn1Record> Asn1Record::decode(const uint8_t* data, size_t dataLen, bool lazy)
{
auto record = decodeInternal(data, dataLen ,lazy);
Expand Down Expand Up @@ -327,6 +409,42 @@ namespace pcpp {
}
}

std::string Asn1Record::toString()
{
auto lines = toStringList();

auto commaSeparated = [](std::string str1, std::string str2)
{
return std::move(str1) + '\n' + std::move(str2);
};

return std::accumulate(std::next(lines.begin()), lines.end(), lines[0], commaSeparated);
}

std::vector<std::string> Asn1Record::toStringList()
{
std::ostringstream stream;

auto universalType = getUniversalTagType();
if (universalType == Asn1UniversalTagType::NotApplicable)
{
stream << pcpp::toString(m_TagClass) << " (" << static_cast<int>(m_TagType) << ")";
}
else
{
stream << pcpp::toString(universalType);
}

if (m_IsConstructed)
{
stream << " (constructed)";
}

stream << ", Length: " << m_TotalLength - m_ValueLength << "+" << m_ValueLength;

return {stream.str()};
}

Asn1GenericRecord::Asn1GenericRecord(Asn1TagClass tagClass, bool isConstructed, uint8_t tagType, const uint8_t* value, size_t valueLen)
{
m_TagType = tagType;
Expand Down Expand Up @@ -409,12 +527,26 @@ namespace pcpp {
return result;
}

std::vector<std::string> Asn1ConstructedRecord::toStringList()
{
decodeValueIfNeeded();
std::vector<std::string> result = {Asn1Record::toStringList().front()};
for (auto subRecord : m_SubRecords)
{
for (const auto& line : subRecord->toStringList())
{
result.push_back(" " + line);
}
}
return result;
}

Asn1SequenceRecord::Asn1SequenceRecord(const std::vector<Asn1Record*>& subRecords)
: Asn1ConstructedRecord(Asn1TagClass::Universal, static_cast<uint8_t>(Asn1UniversalTagType::Sequence), subRecords)
{}

Asn1SetRecord::Asn1SetRecord(const std::vector<Asn1Record*>& subRecords)
: Asn1ConstructedRecord(Asn1TagClass::Universal, static_cast<uint8_t>(Asn1UniversalTagType::Set), subRecords)
: Asn1ConstructedRecord(Asn1TagClass::Universal, static_cast<uint8_t>(Asn1UniversalTagType::Set), subRecords)
{}

Asn1PrimitiveRecord::Asn1PrimitiveRecord(Asn1UniversalTagType tagType) : Asn1Record()
Expand Down Expand Up @@ -526,6 +658,11 @@ namespace pcpp {
return result;
}

std::vector<std::string> Asn1IntegerRecord::toStringList()
{
return std::vector<std::string>({Asn1Record::toStringList().front() + ", Value: " + std::to_string(getValue())});
}

Asn1EnumeratedRecord::Asn1EnumeratedRecord(uint32_t value) : Asn1IntegerRecord(value)
{
m_TagType = static_cast<uint8_t>(Asn1UniversalTagType::Enumerated);
Expand All @@ -536,16 +673,55 @@ namespace pcpp {
m_Value = value;
m_ValueLength = value.size();
m_TotalLength = m_ValueLength + 2;
m_IsPrintable = true;
}

Asn1OctetStringRecord::Asn1OctetStringRecord(const uint8_t* value, size_t valueLength) : Asn1PrimitiveRecord(Asn1UniversalTagType::OctetString)
{
m_Value = byteArrayToHexString(value, valueLength);
m_ValueLength = valueLength;
m_TotalLength = m_ValueLength + 2;
m_IsPrintable = false;
}

void Asn1OctetStringRecord::decodeValue(uint8_t* data, bool lazy)
{
m_Value = std::string(reinterpret_cast<char*>(data), m_ValueLength);
auto value = reinterpret_cast<char*>(data);

m_IsPrintable = std::all_of(value, value + m_ValueLength, [](char c) {
return isprint(c);
});

if (m_IsPrintable)
{
m_Value = std::string(value, m_ValueLength);
}
else
{
m_Value = byteArrayToHexString(data, m_ValueLength);
}
}

std::vector<uint8_t> Asn1OctetStringRecord::encodeValue() const
{
return {m_Value.begin(), m_Value.end()};
if (m_IsPrintable)
{
return {m_Value.begin(), m_Value.end()};
}

// converting the hex stream to a byte array.
// The byte array size is half the size of the string
// i.e "1a2b" (length == 4) becomes {0x1a, 0x2b} (length == 2)
auto rawValueSize = static_cast<size_t>(m_Value.size() / 2);
std::vector<uint8_t> rawValue;
rawValue.resize(rawValueSize);
hexStringToByteArray(m_Value, rawValue.data(), rawValueSize);
return rawValue;
}

std::vector<std::string> Asn1OctetStringRecord::toStringList()
{
return {Asn1Record::toStringList().front() + ", Value: " + getValue()};
}

Asn1BooleanRecord::Asn1BooleanRecord(bool value) : Asn1PrimitiveRecord(Asn1UniversalTagType::Boolean)
Expand All @@ -566,6 +742,11 @@ namespace pcpp {
return { byte };
}

std::vector<std::string> Asn1BooleanRecord::toStringList()
{
return {Asn1Record::toStringList().front() + ", Value: " + (getValue() ? "true" : "false")};
}

Asn1NullRecord::Asn1NullRecord() : Asn1PrimitiveRecord(Asn1UniversalTagType::Null)
{
m_ValueLength = 0;
Expand Down
Loading

0 comments on commit 2890a2f

Please sign in to comment.