Skip to content

chore: stage host-profile patch helper #2

chore: stage host-profile patch helper

chore: stage host-profile patch helper #2

name: Agent IED Simulator Host Patch
on:
push:
branches: [agent/iedsim-full-scl-live-store]
paths: [.github/workflows/agent-iedsim-host-patch.yml]
permissions:
contents: write
jobs:
patch:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: agent/iedsim-full-scl-live-store
- name: Apply host-only MMS capacity patch
shell: python
run: |
from pathlib import Path
p = Path('tools/static_ied_server.cpp')
s = p.read_text(encoding='utf-8')
anchor = 'std::atomic_bool g_stop{false};\n'
insert = '''std::atomic_bool g_stop{false};
// Desktop/lab simulator profile. These bounds are deliberately local to this

Check failure on line 24 in .github/workflows/agent-iedsim-host-patch.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/agent-iedsim-host-patch.yml

Invalid workflow file

You have an error in your yaml syntax on line 24
// host executable; the strict embedded MmsStaticObjectTable/DataSetTable limits
// remain unchanged for deterministic MCU builds.
constexpr std::size_t kHostMaximumManifestObjects = 65'536U;
constexpr std::size_t kHostMaximumManifestDataSets = 4'096U;
[[nodiscard]] bool host_identifier_valid(const std::string_view value) noexcept {
if (value.empty() || value.size() > mms::MmsServiceSpanCodec::maximum_identifier_bytes) return false;
for (const char ch : value) {
const auto byte = static_cast<unsigned char>(ch);
if (byte == 0U || byte > 0x7FU) return false;
}
return true;
}
[[nodiscard]] bool host_manifest_tables_valid(
const mms::MmsStaticObjectTable& objects,
const mms::MmsStaticDataSetTable& data_sets) noexcept {
const auto object_span = objects.objects();
if (object_span.empty() || object_span.size() > kHostMaximumManifestObjects) return false;
std::set<std::pair<std::string_view, std::string_view>> object_names;
for (const auto& object : object_span) {
if (!host_identifier_valid(object.domain) || !host_identifier_valid(object.item) ||
object.type_specification.empty() || object.read == nullptr ||
!object_names.emplace(object.domain, object.item).second) return false;
}
const auto data_set_span = data_sets.data_sets();
if (data_set_span.size() > kHostMaximumManifestDataSets) return false;
std::set<std::pair<std::string_view, std::string_view>> data_set_names;
for (const auto& data_set : data_set_span) {
if (!host_identifier_valid(data_set.domain) || !host_identifier_valid(data_set.item) ||
data_set.members.empty() ||
data_set.members.size() > mms::MmsDataSetSpanCodec::maximum_members ||
!data_set_names.emplace(data_set.domain, data_set.item).second) return false;
std::set<std::pair<std::string_view, std::string_view>> member_names;
for (const auto& member : data_set.members) {
if (!host_identifier_valid(member.domain) || !host_identifier_valid(member.item) ||
!member_names.emplace(member.domain, member.item).second) return false;
const mms::MmsObjectNameView name{
mms::MmsObjectNameViewKind::domain_specific,
std::span<const std::uint8_t>{reinterpret_cast<const std::uint8_t*>(member.domain.data()), member.domain.size()},
std::span<const std::uint8_t>{reinterpret_cast<const std::uint8_t*>(member.item.data()), member.item.size()}};
if (objects.find(name) == nullptr) return false;
}
}
return true;
}
'''
assert s.count(anchor) == 1
s = s.replace(anchor, insert, 1)
old = ''' model.values.reserve(mms::MmsStaticObjectTable::maximum_objects);
model.root_trees.reserve(roots.size());
model.root_value_indices.reserve(roots.size());
std::unordered_map<std::string, std::size_t> root_indices;
for (const auto& [domain, item] : roots) {
if (model.values.size() >= mms::MmsStaticObjectTable::maximum_objects) break;'''
new = ''' model.values.reserve(std::min<std::size_t>(
kHostMaximumManifestObjects, roots.size() + parsed_objects.size()));
model.root_trees.reserve(roots.size());
model.root_value_indices.reserve(roots.size());
std::unordered_map<std::string, std::size_t> root_indices;
for (const auto& [domain, item] : roots) {
if (model.values.size() >= kHostMaximumManifestObjects) break;'''
assert s.count(old) == 1
s = s.replace(old, new, 1)
old = ' if (model.values.size() >= mms::MmsStaticObjectTable::maximum_objects) break;\n'
assert s.count(old) >= 1
s = s.replace(old, ' if (model.values.size() >= kHostMaximumManifestObjects) break;\n')
old = ''' model.data_set_storage.reserve(std::min<std::size_t>(
grouped_members.size(), mms::MmsStaticDataSetTable::maximum_data_sets));
for (auto& [name, members] : grouped_members) {
if (model.data_set_storage.size() >= mms::MmsStaticDataSetTable::maximum_data_sets) break;'''
new = ''' model.data_set_storage.reserve(std::min<std::size_t>(
grouped_members.size(), kHostMaximumManifestDataSets));
for (auto& [name, members] : grouped_members) {
if (model.data_set_storage.size() >= kHostMaximumManifestDataSets) break;'''
assert s.count(old) == 1
s = s.replace(old, new, 1)
old = ''' if (!object_table.valid() ||
!data_sets.valid() ||
!data_sets.valid_against(object_table)) {
throw std::runtime_error("Static MMS server model is invalid.");
}'''
new = ''' const bool model_valid = manifest_model.objects.empty()
? (object_table.valid() && data_sets.valid() && data_sets.valid_against(object_table))
: host_manifest_tables_valid(object_table, data_sets);
if (!model_valid) {
throw std::runtime_error("Static MMS server model is invalid.");
}'''
assert s.count(old) == 1
s = s.replace(old, new, 1)
p.write_text(s, encoding='utf-8')
- name: Commit host-only patch
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add tools/static_ied_server.cpp
git commit -m "feat(iedsim): expand host MMS model capacity"
git push origin HEAD:agent/iedsim-full-scl-live-store