Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
This commit is contained in:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1,90 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2022 - 2022 Pionix GmbH and Contributors to EVerest
#include <slac/channel.hpp>
#include <algorithm>
#include <cassert>
#include <cstring>
#include "packet_socket.hpp"
namespace slac {
Channel::Channel() : socket(nullptr){};
bool Channel::open(const std::string& interface_name) {
did_timeout = false;
auto if_info = ::utils::InterfaceInfo(interface_name);
if (!if_info.is_valid()) {
error = if_info.get_error();
return false;
}
memcpy(orig_if_mac, if_info.get_mac(), sizeof(orig_if_mac));
socket = std::make_unique<::utils::PacketSocket>(if_info, defs::ETH_P_HOMEPLUG_GREENPHY);
if (!socket->is_valid()) {
error = socket->get_error();
socket.reset();
return false;
}
return true;
}
Channel::~Channel() = default;
bool Channel::read(slac::messages::HomeplugMessage& msg, int timeout) {
did_timeout = false;
using IOResult = ::utils::PacketSocket::IOResult;
if (socket) {
switch (socket->read(reinterpret_cast<uint8_t*>(msg.get_raw_message_ptr()), timeout)) {
// FIXME (aw): this enum conversion looks ugly
case IOResult::Failure:
error = socket->get_error();
return false;
case IOResult::Timeout:
did_timeout = true;
return false;
case IOResult::Ok:
return true;
}
}
error = "No IO socket available\n";
return false;
}
bool Channel::write(slac::messages::HomeplugMessage& msg, int timeout) {
using IOResult = ::utils::PacketSocket::IOResult;
assert(("Homeplug message is not valid\n", msg.is_valid()));
did_timeout = false;
if (socket) {
auto raw_msg_ether_shost = msg.get_src_mac();
if (!msg.keep_source_mac()) {
memcpy(raw_msg_ether_shost, orig_if_mac, sizeof(orig_if_mac));
}
switch (socket->write(msg.get_raw_message_ptr(), msg.get_raw_msg_len(), timeout)) {
case IOResult::Failure:
error = socket->get_error();
return false;
case IOResult::Timeout:
did_timeout = true;
return false;
case IOResult::Ok:
return true;
}
}
error = "No IO socket available\n";
return false;
}
const uint8_t* Channel::get_mac_addr() {
return orig_if_mac;
}
} // namespace slac

View File

@@ -0,0 +1,148 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2022 - 2022 Pionix GmbH and Contributors to EVerest
#include "packet_socket.hpp"
#include <cstring>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <linux/if_packet.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
namespace utils {
InterfaceInfo::InterfaceInfo(const std::string& interface_name) {
// fetch all interfaces
struct ifaddrs* if_addrs;
int ret = getifaddrs(&if_addrs);
if (ret == -1) {
error = "Error while calling getifaddrs(): ";
error += strerror(errno);
return;
}
// iterate through them and list them
struct ifaddrs* cur_if_addr = if_addrs;
while (cur_if_addr) {
if (cur_if_addr->ifa_addr && cur_if_addr->ifa_addr->sa_family == AF_PACKET) {
if (0 == interface_name.compare(cur_if_addr->ifa_name)) {
const auto* addr_info = reinterpret_cast<struct sockaddr_ll*>(cur_if_addr->ifa_addr);
memcpy(mac, addr_info->sll_addr, 6);
interface_index = addr_info->sll_ifindex;
valid = true;
break;
}
}
cur_if_addr = cur_if_addr->ifa_next;
}
freeifaddrs(if_addrs);
if (!valid) {
error = "Interface " + interface_name + " not found";
}
}
PacketSocket::PacketSocket(const InterfaceInfo& if_info, int protocol) {
// FIXME (aw): do we need to use O_NONBLOCKING?
socket_fd = socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, htons(protocol));
if (socket_fd == -1) {
error = "Couldn't create the socket: ";
error += strerror(errno);
return;
}
// bind this packet socket to a specific interface
struct sockaddr_ll sock_addr = {
AF_PACKET, // sll_family
htons(protocol), // sll_protocol
if_info.get_index(), // sll_ifindex
0x00, // sll_hatype, set on receiving
0x00, // sll_pkttype, set on receiving
ETH_ALEN, // sll_halen
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // sll_addr[8]
};
if (-1 == bind(socket_fd, (struct sockaddr*)&sock_addr, sizeof(sock_addr))) {
error = "Failed to bind the socket: ";
error += strerror(errno);
close(socket_fd);
}
// everything should have worked out
valid = true;
}
PacketSocket::IOResult PacketSocket::read(uint8_t* buffer, int timeout) {
struct pollfd poll_fd = {
socket_fd, // file descriptor
POLLIN, // requested event
0 // returned event
};
int ret = poll(&poll_fd, 1, timeout);
if (-1 == ret) {
error = std::string("poll() failed with: ") + strerror(errno);
return IOResult::Failure;
}
if (0 == ret) {
return IOResult::Timeout;
}
if ((poll_fd.revents & POLLIN) == 0) {
error = "poll() set other flag than POLLIN";
return IOResult::Failure;
}
bytes_read = ::read(socket_fd, buffer, MIN_BUFFER_SIZE);
if (bytes_read == -1) {
error = std::string("read() failed with: ") + strerror(errno);
return IOResult::Failure;
}
return IOResult::Ok;
}
PacketSocket::IOResult PacketSocket::write(const void* buf, size_t size, int timeout) {
struct pollfd poll_fd = {
socket_fd, // file descriptor
POLLOUT, // requested event
0 // returned event
};
int ret = poll(&poll_fd, 1, timeout);
if (ret == -1) {
error = std::string("poll() failed with: ") + strerror(errno);
return IOResult::Failure;
}
if (ret == 0) {
return IOResult::Timeout;
}
if ((poll_fd.revents & POLLOUT) == 0) {
error = "poll() set other flag than POLLOUT";
return IOResult::Failure;
}
auto bytes_written = ::write(socket_fd, buf, size);
if (-1 == bytes_written) {
error = std::string("write() failed with: ") + strerror(errno);
return IOResult::Failure;
}
if (bytes_written != size) {
error = "write() only send part of the datagram - this should not happen";
return IOResult::Failure;
}
return IOResult::Ok;
}
} // namespace utils

View File

@@ -0,0 +1,72 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2022 - 2022 Pionix GmbH and Contributors to EVerest
#ifndef SRC_PACKET_SOCKET_HPP
#define SRC_PACKET_SOCKET_HPP
#include <cstdint>
#include <string>
#include <linux/if_ether.h>
namespace utils {
class InterfaceInfo {
public:
explicit InterfaceInfo(const std::string& interface_name);
bool is_valid() {
return valid;
};
const std::string& get_error() const {
return error;
};
const int get_index() const {
return interface_index;
};
const uint8_t* get_mac() const {
return mac;
};
private:
bool valid{false};
int interface_index{0};
std::string error;
uint8_t mac[ETH_ALEN];
};
class PacketSocket {
public:
enum class IOResult {
Ok,
Failure,
Timeout
};
PacketSocket(const InterfaceInfo& if_info, int protocol);
bool is_valid() {
return valid;
};
const std::string& get_error() {
return error;
}
IOResult read(uint8_t* buffer, int timeout);
int get_last_read_size() const {
return bytes_read;
};
IOResult write(const void* buf, size_t count, int timeout);
static const int MIN_BUFFER_SIZE = ETH_FRAME_LEN;
private:
int bytes_read{-1};
bool valid{false};
std::string error;
int socket_fd{-1};
};
} // namespace utils
#endif // SRC_PACKET_SOCKET_HPP

View File

@@ -0,0 +1,135 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2022 - 2022 Pionix GmbH and Contributors to EVerest
#include <slac/slac.hpp>
#include <algorithm>
#include <cassert>
#include <cstring>
#include <arpa/inet.h>
#include <endian.h>
#include <everest/tls/openssl_util.hpp>
#include <stdexcept>
namespace slac {
namespace utils {
// note on byte order:
// - sha256 takes the most significant byte first from the lowest
// memory address
// - for the generation of the aes-128, or NMK-HS, the first octet of
// the sha256 output is taken as the zero octet for the NMK-HS
// - for the generation of NID, the NMK is fed into sha256, so having
// a const char* as input should be the proper byte ordering already
void generate_nmk_hs(uint8_t nmk_hs[slac::defs::NMK_LEN], const char* plain_password, int password_len) {
// do pbkdf1 (use sha256 as hashing function, iterate 1000 times,
// use salt)
std::vector<std::uint8_t> input(plain_password, plain_password + password_len);
input.insert(input.end(), slac::defs::NMK_HASH_ARR.begin(), slac::defs::NMK_HASH_ARR.end());
openssl::sha_256_digest_t digest;
openssl::sha_256(input.data(), input.size(), digest);
for (int i = 0; i < 1000 - 1; ++i) {
openssl::sha_256(digest.data(), openssl::sha_256_digest_size, digest);
}
memcpy(nmk_hs, digest.data(), slac::defs::NMK_LEN);
}
void generate_nid_from_nmk(uint8_t nid[slac::defs::NID_LEN], const uint8_t nmk[slac::defs::NMK_LEN]) {
// msb of least significant octet of NMK should be the leftmost bit
// of the input, which corresponds to the usual const char* order
// do pbkdf1 (use sha256 as hashing function, iterate 5 times, no
// salt)
openssl::sha_256_digest_t digest;
openssl::sha_256(nmk, slac::defs::NMK_LEN, digest);
for (int i = 0; i < 5 - 1; ++i) {
openssl::sha_256(digest.data(), openssl::sha_256_digest_size, digest);
}
// use leftmost 52 bits of the hash output
// left most bit should be bit 7 of the nid
memcpy(nid, digest.data(), slac::defs::NID_LEN - 1); // (bits 52 - 5)
nid[slac::defs::NID_LEN - 1] =
(slac::defs::NID_SECURITY_LEVEL_SIMPLE_CONNECT << slac::defs::NID_SECURITY_LEVEL_OFFSET) |
((static_cast<uint8_t>(digest.data()[6])) >> slac::defs::NID_MOST_SIGNIFANT_BYTE_SHIFT);
}
} // namespace utils
namespace messages {
static constexpr auto effective_payload_length(const defs::MMV mmv) {
if (mmv == defs::MMV::AV_1_0) {
return sizeof(homeplug_message::payload);
} else {
return sizeof(homeplug_message::payload) - sizeof(homeplug_fragmentation_part);
}
}
void HomeplugMessage::setup_payload(void const* payload, int len, uint16_t mmtype, const defs::MMV mmv) {
if (len > effective_payload_length(mmv)) {
throw std::runtime_error("Homeplug Payload length too long");
}
raw_msg.homeplug_header.mmv = static_cast<std::underlying_type_t<defs::MMV>>(mmv);
raw_msg.homeplug_header.mmtype = htole16(mmtype);
uint8_t* dst = raw_msg.payload;
if (mmv != defs::MMV::AV_1_0) {
homeplug_fragmentation_part fragmentation_part{};
fragmentation_part.fmni = 0; // not implemented
fragmentation_part.fmsn = 0; // not implemented
memcpy(dst, &fragmentation_part, sizeof(fragmentation_part));
dst += sizeof(fragmentation_part); // adjust effective payload start
}
// copy payload into place
memcpy(dst, payload, len);
// get pointer to the end of buffer
uint8_t* dst_end = dst + len;
// calculate raw message length
raw_msg_len = dst_end - reinterpret_cast<uint8_t*>(&raw_msg);
// do padding
auto padding_len = defs::MME_MIN_LENGTH - raw_msg_len;
if (padding_len > 0) {
memset(dst_end, 0x00, padding_len);
raw_msg_len = defs::MME_MIN_LENGTH;
}
}
void HomeplugMessage::setup_ethernet_header(const uint8_t dst_mac_addr[ETH_ALEN],
const uint8_t src_mac_addr[ETH_ALEN]) {
// ethernet frame byte order is big endian
raw_msg.ethernet_header.ether_type = htons(defs::ETH_P_HOMEPLUG_GREENPHY);
if (dst_mac_addr) {
memcpy(raw_msg.ethernet_header.ether_dhost, dst_mac_addr, ETH_ALEN);
}
if (src_mac_addr) {
memcpy(raw_msg.ethernet_header.ether_shost, src_mac_addr, ETH_ALEN);
keep_src_mac = true;
} else {
keep_src_mac = false;
}
}
uint16_t HomeplugMessage::get_mmtype() const {
return le16toh(raw_msg.homeplug_header.mmtype);
}
uint8_t* HomeplugMessage::get_src_mac() {
return raw_msg.ethernet_header.ether_shost;
}
bool HomeplugMessage::is_valid() const {
return raw_msg_len >= defs::MME_MIN_LENGTH;
}
} // namespace messages
} // namespace slac