Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
env:
CARGO_INCREMENTAL: '0'
CARGO_PROFILE_DEV_DEBUG: line-tables-only
CARGO_PROFILE_TEST_DEBUG: line-tables-only
jobs:
contracts:
name: Contracts, storage and inventory (Rust, Vue, Chromium)
Expand All @@ -20,14 +24,21 @@ jobs:
uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2
with:
args: --offline --include-fragments --no-progress README.md CONTRIBUTING.md SECURITY.md docs/contracts.md docs/storage.md docs/inventory.md
- run: corepack enable
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 24
cache: yarn
cache-dependency-path: yarn.lock
- uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
with:
toolchain: stable
components: rustfmt, clippy
targets: thumbv7em-none-eabihf
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2
with:
cache-bin: 'false'
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: arduino/setup-protoc@c65c819552d16ad3c9b72d9dfd5ba5237b9c906b # v3.0.0
with:
version: "36.2"
Expand All @@ -36,10 +47,14 @@ jobs:
with:
tool: nextest
fallback: none
- run: corepack enable
- run: yarn install --immutable
- run: yarn playwright install --with-deps chromium
- run: sh scripts/check-contracts.sh
- name: Report disk usage
if: always()
run: |
df -h .
if [ -d target ]; then du -sh target; fi
native-observation:
name: Native observation (${{ matrix.os }})
strategy:
Expand All @@ -55,6 +70,10 @@ jobs:
with:
toolchain: stable
components: rustfmt, clippy
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2
with:
cache-bin: 'false'
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: arduino/setup-protoc@c65c819552d16ad3c9b72d9dfd5ba5237b9c906b # v3.0.0
with:
version: "36.2"
Expand All @@ -66,4 +85,4 @@ jobs:
- run: cargo build --locked -p omloom-observe
- run: cargo clippy --locked -p omloom-observe --all-targets -- -D warnings
- run: cargo nextest run --locked -p omloom-observe
- run: cargo clean
- run: cargo clean -p omloom-observe
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ as current inventory, independently of discovery or execution adapters.
Devices can retain [partial equipment knowledge](docs/inventory.md#partly-identified-equipment),
including bounded quantities, model alternatives and supported exclusions,
then be refined without changing their identity.
The workspace can [check recorded requirements](docs/inventory.md#recorded-requirements)
against permitted current facts, preserving missing, stale and conflicting evidence.

## License

Expand Down
4 changes: 4 additions & 0 deletions crates/omloom-contracts/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ fn main() -> Result<(), Box<dyn Error>> {
&& matches!(field.name(), "candidates" | "excluded")
{
16
} else if (message.name() == "EvaluateRequirements" && field.name() == "nodes")
|| (message.name() == "RequirementGroup" && field.name() == "members")
{
64
} else {
0
}
Expand Down
11 changes: 10 additions & 1 deletion crates/omloom-contracts/src/preflight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ impl Work {
.find(|f| f.tag == tag)
.ok_or(Error::UnknownField)?;
let mask = 1u64.checked_shl(tag).ok_or(Error::Wire)?;
if shape.max_count != 0 {
if shape.max_count != 0
&& !(shape.repeated && matches!(shape.kind, Kind::Varint(_)) && key & 7 == 2)
{
let count = &mut counts[tag as usize];
if *count == shape.max_count {
return Err(Error::Limit);
Expand Down Expand Up @@ -184,6 +186,13 @@ impl Work {
let mut packed = body;
while !packed.is_empty() {
self.field()?;
if shape.max_count != 0 {
let count = &mut counts[tag as usize];
if *count == shape.max_count {
return Err(Error::Limit);
}
*count += 1;
}
if varint(&mut packed)? > max {
return Err(Error::Wire);
}
Expand Down
65 changes: 65 additions & 0 deletions crates/omloom-contracts/tests/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,68 @@ fn repeated_empty_messages_cannot_amplify_small_frames() {
));
assert_eq!(budget.used(), 0);
}

// Packed scalar references count individually before allocation, not once per buffer.
#[test]
fn requirements_bound_nodes_and_packed_members_before_decoding() {
let budget = MemoryBudget::new(16 * 1_048_576);
for (nodes, members) in [(65, 0), (1, 65)] {
let node = api::RequirementNode {
expression: Some(api::requirement_node::Expression::Group(
api::RequirementGroup {
kind: api::RequirementGroupKind::All as i32,
members: vec![0; members],
minimum: 0,
},
)),
};
let bytes = api::Request {
command: Some(api::request::Command::EvaluateRequirements(
api::EvaluateRequirements {
nodes: vec![node; nodes],
..Default::default()
},
)),
working: None,
}
.encode_to_vec();
assert!(matches!(
decode_workspace(&bytes, Limits::default(), &budget),
Err(Error::Limit)
));
assert_eq!(budget.used(), 0);
}
}

#[test]
fn requirements_share_member_limits_across_packed_and_unpacked_segments() {
// Splitting one repeated field across wire encodings cannot reset its allocation bound.
fn message(tag: u32, body: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
prost::encoding::encode_key(tag, prost::encoding::WireType::LengthDelimited, &mut out);
prost::encoding::encode_varint(body.len() as u64, &mut out);
out.extend_from_slice(body);
out
}
let budget = MemoryBudget::new(16 * 1_048_576);
for (unpacked, extra) in [(true, 32), (true, 33), (false, 33)] {
let mut group = vec![0x08, 1];
group.extend(message(2, &[0; 32]));
if unpacked {
for _ in 0..extra {
group.extend([0x10, 0]);
}
} else {
group.extend(message(2, &vec![0; extra]));
}
let bytes = message(33, &message(2, &message(2, &group)));
let result = decode_workspace(&bytes, Limits::default(), &budget);
if extra == 32 {
assert!(result.is_ok());
} else {
assert!(matches!(result, Err(Error::Limit)));
}
drop(result);
assert_eq!(budget.used(), 0);
}
}
7 changes: 7 additions & 0 deletions crates/omloom-store/src/disk/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod interface_facts;
mod network;
pub(super) mod network_facts;
mod projection;
mod requirements;
mod responsibility;
mod software;
mod source_layout;
Expand Down Expand Up @@ -337,6 +338,12 @@ impl Store {
value,
working.as_ref().ok_or(Error::Invalid)?,
),
C::EvaluateRequirements(value) => self.workspace_requirements(
context,
value,
working.as_ref().ok_or(Error::Invalid)?,
now,
),
C::Export(export) => self.workspace_export(context, export),
C::Network(network) => self.workspace_network(
context,
Expand Down
Loading
Loading