blob: 33b53c17127783b17df366028c6256c2223dacd6 [file] [log] [blame]
#include "message_parsers.hpp"
#include "endian.hpp"
#include "main.hpp"
#include "message.hpp"
#include "sessions_manager.hpp"
#include <memory>
namespace message
{
namespace parser
{
std::tuple<std::shared_ptr<Message>, SessionHeader>
unflatten(std::vector<uint8_t>& inPacket)
{
// Check if the packet has atleast the size of the RMCP Header
if (inPacket.size() < sizeof(RmcpHeader_t))
{
throw std::runtime_error("RMCP Header missing");
}
auto rmcpHeaderPtr = reinterpret_cast<RmcpHeader_t*>(inPacket.data());
// Verify if the fields in the RMCP header conforms to the specification
if ((rmcpHeaderPtr->version != RMCP_VERSION) ||
(rmcpHeaderPtr->rmcpSeqNum != RMCP_SEQ) ||
(rmcpHeaderPtr->classOfMsg < static_cast<uint8_t>(ClassOfMsg::ASF) &&
rmcpHeaderPtr->classOfMsg > static_cast<uint8_t>(ClassOfMsg::OEM)))
{
throw std::runtime_error("RMCP Header is invalid");
}
if (rmcpHeaderPtr->classOfMsg == static_cast<uint8_t>(ClassOfMsg::ASF))
{
#ifndef RMCP_PING
throw std::runtime_error("RMCP Ping is not supported");
#else
return std::make_tuple(asfparser::unflatten(inPacket),
SessionHeader::IPMI15);
#endif // RMCP_PING
}
auto sessionHeaderPtr = reinterpret_cast<BasicHeader_t*>(inPacket.data());
// Read the Session Header and invoke the parser corresponding to the
// header type
switch (static_cast<SessionHeader>(sessionHeaderPtr->format.formatType))
{
case SessionHeader::IPMI15:
{
return std::make_tuple(ipmi15parser::unflatten(inPacket),
SessionHeader::IPMI15);
}
case SessionHeader::IPMI20:
{
return std::make_tuple(ipmi20parser::unflatten(inPacket),
SessionHeader::IPMI20);
}
default:
{
throw std::runtime_error("Invalid Session Header");
}
}
}
std::vector<uint8_t> flatten(const std::shared_ptr<Message>& outMessage,
SessionHeader authType,
const std::shared_ptr<session::Session>& session)
{
// Call the flatten routine based on the header type
switch (authType)
{
case SessionHeader::IPMI15:
{
return ipmi15parser::flatten(outMessage, session);
}
case SessionHeader::IPMI20:
{
return ipmi20parser::flatten(outMessage, session);
}
default:
{
return {};
}
}
}
} // namespace parser
namespace ipmi15parser
{
std::shared_ptr<Message> unflatten(std::vector<uint8_t>& inPacket)
{
if (inPacket.size() < sizeof(SessionHeader_t))
{
throw std::runtime_error("IPMI1.5 Session Header Missing");
}
auto header = reinterpret_cast<SessionHeader_t*>(inPacket.data());
uint32_t sessionID = endian::from_ipmi(header->sessId);
if (sessionID != session::sessionZero)
{
throw std::runtime_error("IPMI1.5 session packets are unsupported");
}
auto message = std::make_shared<Message>();
message->payloadType = PayloadType::IPMI;
message->bmcSessionID = session::sessionZero;
message->sessionSeqNum = endian::from_ipmi(header->sessSeqNum);
message->isPacketEncrypted = false;
message->isPacketAuthenticated = false;
message->rmcpMsgClass =
static_cast<ClassOfMsg>(header->base.rmcp.classOfMsg);
// Confirm the number of data bytes received correlates to
// the packet length in the header
size_t payloadLen = header->payloadLength;
if ((payloadLen == 0) || (inPacket.size() < (sizeof(*header) + payloadLen)))
{
throw std::runtime_error("Invalid data length");
}
(message->payload)
.assign(inPacket.data() + sizeof(SessionHeader_t),
inPacket.data() + sizeof(SessionHeader_t) + payloadLen);
return message;
}
std::vector<uint8_t>
flatten(const std::shared_ptr<Message>& outMessage,
const std::shared_ptr<session::Session>& /* session */)
{
std::vector<uint8_t> packet(sizeof(SessionHeader_t));
// Insert Session Header into the Packet
auto header = reinterpret_cast<SessionHeader_t*>(packet.data());
header->base.rmcp.version = parser::RMCP_VERSION;
header->base.rmcp.reserved = 0x00;
header->base.rmcp.rmcpSeqNum = parser::RMCP_SEQ;
header->base.rmcp.classOfMsg = static_cast<uint8_t>(ClassOfMsg::IPMI);
header->base.format.formatType =
static_cast<uint8_t>(parser::SessionHeader::IPMI15);
header->sessSeqNum = 0;
header->sessId = endian::to_ipmi(outMessage->rcSessionID);
header->payloadLength = static_cast<uint8_t>(outMessage->payload.size());
// Insert the Payload into the Packet
packet.insert(packet.end(), outMessage->payload.begin(),
outMessage->payload.end());
// Insert the Session Trailer
packet.resize(packet.size() + sizeof(SessionTrailer_t));
auto trailer =
reinterpret_cast<SessionTrailer_t*>(packet.data() + packet.size());
trailer->legacyPad = 0x00;
return packet;
}
} // namespace ipmi15parser
namespace ipmi20parser
{
std::shared_ptr<Message> unflatten(std::vector<uint8_t>& inPacket)
{
// Check if the packet has atleast the Session Header
if (inPacket.size() < sizeof(SessionHeader_t))
{
throw std::runtime_error("IPMI2.0 Session Header Missing");
}
auto header = reinterpret_cast<SessionHeader_t*>(inPacket.data());
uint32_t sessionID = endian::from_ipmi(header->sessId);
auto session = session::Manager::get().getSession(sessionID);
if (!session)
{
throw std::runtime_error("RMCP+ message from unknown session");
}
auto message = std::make_shared<Message>();
message->payloadType = static_cast<PayloadType>(header->payloadType & 0x3F);
message->bmcSessionID = sessionID;
message->sessionSeqNum = endian::from_ipmi(header->sessSeqNum);
message->isPacketEncrypted =
((header->payloadType & PAYLOAD_ENCRYPT_MASK) ? true : false);
message->isPacketAuthenticated =
((header->payloadType & PAYLOAD_AUTH_MASK) ? true : false);
message->rmcpMsgClass =
static_cast<ClassOfMsg>(header->base.rmcp.classOfMsg);
// Confirm the number of data bytes received correlates to
// the packet length in the header
size_t payloadLen = endian::from_ipmi(header->payloadLength);
if ((payloadLen == 0) || (inPacket.size() < (sizeof(*header) + payloadLen)))
{
throw std::runtime_error("Invalid data length");
}
bool integrityMismatch = session->isIntegrityAlgoEnabled() &&
!message->isPacketAuthenticated;
bool encryptMismatch = session->isCryptAlgoEnabled() &&
!message->isPacketEncrypted;
if (sessionID != session::sessionZero &&
(integrityMismatch || encryptMismatch))
{
throw std::runtime_error("unencrypted or unauthenticated message");
}
if (message->isPacketAuthenticated)
{
if (!(internal::verifyPacketIntegrity(inPacket, message, payloadLen,
session)))
{
throw std::runtime_error("Packet Integrity check failed");
}
}
// Decrypt the payload if the payload is encrypted
if (message->isPacketEncrypted)
{
// Assign the decrypted payload to the IPMI Message
message->payload = internal::decryptPayload(inPacket, message,
payloadLen, session);
}
else
{
message->payload.assign(inPacket.begin() + sizeof(SessionHeader_t),
inPacket.begin() + sizeof(SessionHeader_t) +
payloadLen);
}
return message;
}
std::vector<uint8_t> flatten(const std::shared_ptr<Message>& outMessage,
const std::shared_ptr<session::Session>& session)
{
std::vector<uint8_t> packet(sizeof(SessionHeader_t));
SessionHeader_t* header = reinterpret_cast<SessionHeader_t*>(packet.data());
header->base.rmcp.version = parser::RMCP_VERSION;
header->base.rmcp.reserved = 0x00;
header->base.rmcp.rmcpSeqNum = parser::RMCP_SEQ;
header->base.rmcp.classOfMsg = static_cast<uint8_t>(ClassOfMsg::IPMI);
header->base.format.formatType =
static_cast<uint8_t>(parser::SessionHeader::IPMI20);
header->payloadType = static_cast<uint8_t>(outMessage->payloadType);
header->sessId = endian::to_ipmi(outMessage->rcSessionID);
// Add session sequence number
internal::addSequenceNumber(packet, session);
size_t payloadLen = 0;
// Encrypt the payload if needed
if (outMessage->isPacketEncrypted)
{
header->payloadType |= PAYLOAD_ENCRYPT_MASK;
auto cipherPayload = internal::encryptPayload(outMessage, session);
payloadLen = cipherPayload.size();
header->payloadLength = endian::to_ipmi<uint16_t>(cipherPayload.size());
// Insert the encrypted payload into the outgoing IPMI packet
packet.insert(packet.end(), cipherPayload.begin(), cipherPayload.end());
}
else
{
header->payloadLength =
endian::to_ipmi<uint16_t>(outMessage->payload.size());
payloadLen = outMessage->payload.size();
// Insert the Payload into the Packet
packet.insert(packet.end(), outMessage->payload.begin(),
outMessage->payload.end());
}
if (outMessage->isPacketAuthenticated)
{
header = reinterpret_cast<SessionHeader_t*>(packet.data());
header->payloadType |= PAYLOAD_AUTH_MASK;
internal::addIntegrityData(packet, outMessage, payloadLen, session);
}
return packet;
}
namespace internal
{
void addSequenceNumber(std::vector<uint8_t>& packet,
const std::shared_ptr<session::Session>& session)
{
SessionHeader_t* header = reinterpret_cast<SessionHeader_t*>(packet.data());
if (header->sessId == session::sessionZero)
{
header->sessSeqNum = 0x00;
}
else
{
auto seqNum = session->sequenceNums.increment();
header->sessSeqNum = endian::to_ipmi(seqNum);
}
}
bool verifyPacketIntegrity(const std::vector<uint8_t>& packet,
const std::shared_ptr<Message>& /* message */,
size_t payloadLen,
const std::shared_ptr<session::Session>& session)
{
/*
* Padding bytes are added to cause the number of bytes in the data range
* covered by the AuthCode(Integrity Data) field to be a multiple of 4 bytes
* .If present each integrity Pad byte is set to FFh. The following logic
* calculates the number of padding bytes added in the IPMI packet.
*/
auto paddingLen = 4 - ((payloadLen + 2) & 3);
auto sessTrailerPos = sizeof(SessionHeader_t) + payloadLen + paddingLen;
// verify packet size includes trailer struct starts at sessTrailerPos
if (packet.size() < (sessTrailerPos + sizeof(SessionTrailer_t)))
{
return false;
}
auto trailer = reinterpret_cast<const SessionTrailer_t*>(packet.data() +
sessTrailerPos);
// Check trailer->padLength against paddingLen, both should match up,
// return false if the lengths don't match
if (trailer->padLength != paddingLen)
{
return false;
}
auto integrityAlgo = session->getIntegrityAlgo();
// Check if Integrity data length is as expected, check integrity data
// length is same as the length expected for the Integrity Algorithm that
// was negotiated during the session open process.
if ((packet.size() - sessTrailerPos - sizeof(SessionTrailer_t)) !=
integrityAlgo->authCodeLength)
{
return false;
}
auto integrityIter = packet.cbegin();
std::advance(integrityIter, sessTrailerPos + sizeof(SessionTrailer_t));
// The integrity data is calculated from the AuthType/Format field up to and
// including the field that immediately precedes the AuthCode field itself.
size_t length = packet.size() - integrityAlgo->authCodeLength -
message::parser::RMCP_SESSION_HEADER_SIZE;
return integrityAlgo->verifyIntegrityData(packet, length, integrityIter,
packet.cend());
}
void addIntegrityData(std::vector<uint8_t>& packet,
const std::shared_ptr<Message>& /* message */,
size_t payloadLen,
const std::shared_ptr<session::Session>& session)
{
// The following logic calculates the number of padding bytes to be added to
// IPMI packet. If needed each integrity Pad byte is set to FFh.
auto paddingLen = 4 - ((payloadLen + 2) & 3);
packet.insert(packet.end(), paddingLen, 0xFF);
packet.resize(packet.size() + sizeof(SessionTrailer_t));
auto trailer = reinterpret_cast<SessionTrailer_t*>(
packet.data() + packet.size() - sizeof(SessionTrailer_t));
trailer->padLength = paddingLen;
trailer->nextHeader = parser::RMCP_MESSAGE_CLASS_IPMI;
auto integrityData =
session->getIntegrityAlgo()->generateIntegrityData(packet);
packet.insert(packet.end(), integrityData.begin(), integrityData.end());
}
std::vector<uint8_t>
decryptPayload(const std::vector<uint8_t>& packet,
const std::shared_ptr<Message>& /* message */,
size_t payloadLen,
const std::shared_ptr<session::Session>& session)
{
return session->getCryptAlgo()->decryptPayload(
packet, sizeof(SessionHeader_t), payloadLen);
}
std::vector<uint8_t>
encryptPayload(const std::shared_ptr<Message>& message,
const std::shared_ptr<session::Session>& session)
{
return session->getCryptAlgo()->encryptPayload(message->payload);
}
} // namespace internal
} // namespace ipmi20parser
#ifdef RMCP_PING
namespace asfparser
{
std::shared_ptr<Message> unflatten(std::vector<uint8_t>& inPacket)
{
auto message = std::make_shared<Message>();
auto header = reinterpret_cast<AsfMessagePing_t*>(inPacket.data());
message->payloadType = PayloadType::IPMI;
message->rmcpMsgClass = ClassOfMsg::ASF;
message->asfMsgTag = header->msgTag;
return message;
}
std::vector<uint8_t> flatten(uint8_t asfMsgTag)
{
std::vector<uint8_t> packet(sizeof(AsfMessagePong_t));
// Insert RMCP header into the Packet
auto header = reinterpret_cast<AsfMessagePong_t*>(packet.data());
header->ping.rmcp.version = parser::RMCP_VERSION;
header->ping.rmcp.reserved = 0x00;
header->ping.rmcp.rmcpSeqNum = parser::RMCP_SEQ;
header->ping.rmcp.classOfMsg = static_cast<uint8_t>(ClassOfMsg::ASF);
// No OEM-specific capabilities exist, therefore the second
// IANA Enterprise Number contains the same IANA(4542)
header->ping.iana = header->iana = endian::to_ipmi(parser::ASF_IANA);
header->ping.msgType = static_cast<uint8_t>(RmcpMsgType::PONG);
header->ping.msgTag = asfMsgTag;
header->ping.reserved = 0x00;
header->ping.dataLen =
parser::RMCP_ASF_PONG_DATA_LEN; // as per spec 13.2.4,
header->iana = parser::ASF_IANA;
header->oemDefined = 0x00;
header->suppEntities = parser::ASF_SUPP_ENT;
header->suppInteract = parser::ASF_SUPP_INT;
header->reserved1 = 0x00;
header->reserved2 = 0x00;
return packet;
}
} // namespace asfparser
#endif // RMCP_PING
} // namespace message