move docs to docs/ folder, merge architecture files, update references
This commit is contained in:
463
docs/SANDBOX_MILESTONE_18.md
Normal file
463
docs/SANDBOX_MILESTONE_18.md
Normal file
@@ -0,0 +1,463 @@
|
||||
# Milestone 18: Inter-App Communication
|
||||
|
||||
**Status**: Complete
|
||||
**Goal**: Kernel-mediated message passing between apps.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This milestone implements secure inter-app communication via an intent system:
|
||||
- Apps register to receive specific intent actions
|
||||
- Senders must have required permissions
|
||||
- All messages go through kernel mediation (no direct app-to-app)
|
||||
- Message size limits prevent resource exhaustion
|
||||
- Audit logging for all inter-app communication
|
||||
|
||||
### Key Deliverables
|
||||
|
||||
1. **MessageBus class** - Central message routing and validation
|
||||
2. **Intent struct** - Message format with action, type, and data
|
||||
3. **Lua intents API** - `intents.send()`, `intents.on()`, `intents.broadcast()`
|
||||
4. **Security validation** - Permission checks, sender/receiver verification
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
src/main/cpp/sandbox/
|
||||
├── message_bus.h # NEW - Message bus API header
|
||||
└── message_bus.cpp # NEW - Message bus implementation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### 1. MessageBus Class
|
||||
|
||||
```cpp
|
||||
// message_bus.h
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
struct lua_State;
|
||||
|
||||
namespace mosis {
|
||||
|
||||
struct Intent {
|
||||
std::string action; // Intent action (e.g., "share", "view", "edit")
|
||||
std::string type; // MIME type (e.g., "text/plain", "image/png")
|
||||
std::string data; // Intent data/payload
|
||||
std::string from_app; // Sender app ID (set by kernel)
|
||||
};
|
||||
|
||||
struct IntentFilter {
|
||||
std::string action; // Required action
|
||||
std::vector<std::string> types; // Accepted MIME types (empty = all)
|
||||
};
|
||||
|
||||
enum class MessageError {
|
||||
None,
|
||||
NoReceivers,
|
||||
PermissionDenied,
|
||||
InvalidIntent,
|
||||
DataTooLarge,
|
||||
ReceiverNotFound,
|
||||
SenderBlocked
|
||||
};
|
||||
|
||||
class MessageBus {
|
||||
public:
|
||||
using IntentHandler = std::function<void(const Intent&)>;
|
||||
|
||||
MessageBus();
|
||||
~MessageBus();
|
||||
|
||||
// Register an app to receive intents
|
||||
void RegisterReceiver(
|
||||
const std::string& app_id,
|
||||
const IntentFilter& filter,
|
||||
IntentHandler handler
|
||||
);
|
||||
|
||||
// Unregister all handlers for an app
|
||||
void UnregisterApp(const std::string& app_id);
|
||||
|
||||
// Send intent to specific app (requires permission)
|
||||
MessageError Send(
|
||||
const std::string& sender_app,
|
||||
const std::string& target_app,
|
||||
const Intent& intent,
|
||||
std::string& error
|
||||
);
|
||||
|
||||
// Broadcast intent to all registered receivers
|
||||
MessageError Broadcast(
|
||||
const std::string& sender_app,
|
||||
const Intent& intent,
|
||||
std::string& error
|
||||
);
|
||||
|
||||
// Check if any app handles this action
|
||||
bool HasReceiverFor(const std::string& action) const;
|
||||
|
||||
// Get list of apps that handle an action
|
||||
std::vector<std::string> GetReceiversFor(const std::string& action) const;
|
||||
|
||||
// Permission management
|
||||
void SetAppPermission(const std::string& app_id, const std::string& permission, bool granted);
|
||||
bool HasPermission(const std::string& app_id, const std::string& permission) const;
|
||||
|
||||
// Block specific app from sending/receiving
|
||||
void BlockApp(const std::string& app_id);
|
||||
void UnblockApp(const std::string& app_id);
|
||||
bool IsBlocked(const std::string& app_id) const;
|
||||
|
||||
// Clear all registrations (for testing)
|
||||
void Clear();
|
||||
|
||||
// Statistics
|
||||
size_t GetReceiverCount() const;
|
||||
size_t GetMessageCount() const { return m_message_count; }
|
||||
|
||||
private:
|
||||
struct ReceiverEntry {
|
||||
std::string app_id;
|
||||
IntentFilter filter;
|
||||
IntentHandler handler;
|
||||
};
|
||||
|
||||
std::vector<ReceiverEntry> m_receivers;
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>> m_permissions;
|
||||
std::unordered_set<std::string> m_blocked_apps;
|
||||
mutable std::mutex m_mutex;
|
||||
size_t m_message_count = 0;
|
||||
|
||||
static constexpr size_t MAX_DATA_SIZE = 1024 * 1024; // 1MB max
|
||||
|
||||
bool MatchesFilter(const Intent& intent, const IntentFilter& filter) const;
|
||||
bool ValidateIntent(const Intent& intent, std::string& error) const;
|
||||
};
|
||||
|
||||
// Register intents.* APIs as globals
|
||||
void RegisterIntentsAPI(lua_State* L, MessageBus* bus, const std::string& app_id);
|
||||
|
||||
} // namespace mosis
|
||||
```
|
||||
|
||||
### 2. Standard Intent Actions
|
||||
|
||||
| Action | Description | Required Permission |
|
||||
|--------|-------------|---------------------|
|
||||
| `share` | Share content with another app | None |
|
||||
| `view` | Request app to view content | None |
|
||||
| `edit` | Request app to edit content | None |
|
||||
| `pick` | Request user to pick item | None |
|
||||
| `call` | Initiate phone call | `phone.call` |
|
||||
| `message` | Send SMS/message | `sms.send` |
|
||||
| `email` | Compose email | None |
|
||||
|
||||
### 3. Permission Requirements
|
||||
|
||||
| Operation | Permission Required |
|
||||
|-----------|---------------------|
|
||||
| Send intent | Action-specific (see above) |
|
||||
| Broadcast intent | Action-specific |
|
||||
| Register receiver | None |
|
||||
| Receive intent | None |
|
||||
|
||||
### 4. Lua API
|
||||
|
||||
```lua
|
||||
-- Register to receive intents
|
||||
intents.on("share", function(intent)
|
||||
print("Received share from:", intent.from)
|
||||
print("Type:", intent.type)
|
||||
print("Data:", intent.data)
|
||||
end)
|
||||
|
||||
-- Register with MIME type filter
|
||||
intents.on("view", function(intent)
|
||||
-- Handle view intent
|
||||
end, {types = {"image/png", "image/jpeg"}})
|
||||
|
||||
-- Send intent to specific app
|
||||
local success, err = intents.send("target.app.id", {
|
||||
action = "share",
|
||||
type = "text/plain",
|
||||
data = "Hello world"
|
||||
})
|
||||
|
||||
-- Broadcast to all handlers
|
||||
local count = intents.broadcast({
|
||||
action = "share",
|
||||
type = "text/plain",
|
||||
data = "Hello everyone"
|
||||
})
|
||||
|
||||
-- Check if any app handles an action
|
||||
local hasHandler = intents.hasReceiver("share")
|
||||
|
||||
-- Get list of apps that handle an action
|
||||
local apps = intents.getReceivers("share")
|
||||
|
||||
-- Unregister all handlers for this app
|
||||
intents.unregisterAll()
|
||||
```
|
||||
|
||||
### 5. Error Handling
|
||||
|
||||
```lua
|
||||
-- Errors raised via Lua errors
|
||||
local ok, err = pcall(function()
|
||||
intents.send("target.app", {action = "share", data = "test"})
|
||||
end)
|
||||
if not ok then
|
||||
print("Error:", err) -- e.g., "no receivers for action: share"
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test Cases
|
||||
|
||||
### Test 1: Send to Registered Receiver
|
||||
|
||||
```cpp
|
||||
bool Test_MessageBusSendToReceiver(std::string& error_msg) {
|
||||
mosis::MessageBus bus;
|
||||
|
||||
bool received = false;
|
||||
mosis::Intent receivedIntent;
|
||||
|
||||
// Register receiver
|
||||
bus.RegisterReceiver("receiver.app", {"share", {}}, [&](const mosis::Intent& i) {
|
||||
received = true;
|
||||
receivedIntent = i;
|
||||
});
|
||||
|
||||
// Send intent
|
||||
mosis::Intent intent{"share", "text/plain", "Hello", "sender.app"};
|
||||
std::string err;
|
||||
auto result = bus.Send("sender.app", "receiver.app", intent, err);
|
||||
|
||||
EXPECT_TRUE(result == mosis::MessageError::None);
|
||||
EXPECT_TRUE(received);
|
||||
EXPECT_TRUE(receivedIntent.action == "share");
|
||||
EXPECT_TRUE(receivedIntent.data == "Hello");
|
||||
EXPECT_TRUE(receivedIntent.from_app == "sender.app");
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### Test 2: Block Unregistered Action
|
||||
|
||||
```cpp
|
||||
bool Test_MessageBusBlockUnregistered(std::string& error_msg) {
|
||||
mosis::MessageBus bus;
|
||||
|
||||
// No receivers registered
|
||||
mosis::Intent intent{"share", "text/plain", "Hello", "sender.app"};
|
||||
std::string err;
|
||||
auto result = bus.Send("sender.app", "receiver.app", intent, err);
|
||||
|
||||
EXPECT_TRUE(result == mosis::MessageError::ReceiverNotFound);
|
||||
EXPECT_TRUE(err.find("not found") != std::string::npos ||
|
||||
err.find("no receiver") != std::string::npos);
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### Test 3: Broadcast to Multiple Receivers
|
||||
|
||||
```cpp
|
||||
bool Test_MessageBusBroadcast(std::string& error_msg) {
|
||||
mosis::MessageBus bus;
|
||||
|
||||
int receiveCount = 0;
|
||||
|
||||
// Register multiple receivers for same action
|
||||
bus.RegisterReceiver("app1", {"share", {}}, [&](const mosis::Intent&) { receiveCount++; });
|
||||
bus.RegisterReceiver("app2", {"share", {}}, [&](const mosis::Intent&) { receiveCount++; });
|
||||
bus.RegisterReceiver("app3", {"other", {}}, [&](const mosis::Intent&) { receiveCount++; });
|
||||
|
||||
// Broadcast share intent
|
||||
mosis::Intent intent{"share", "text/plain", "Hello", "sender.app"};
|
||||
std::string err;
|
||||
auto result = bus.Broadcast("sender.app", intent, err);
|
||||
|
||||
EXPECT_TRUE(result == mosis::MessageError::None);
|
||||
EXPECT_TRUE(receiveCount == 2); // Only app1 and app2
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### Test 4: MIME Type Filtering
|
||||
|
||||
```cpp
|
||||
bool Test_MessageBusMimeFilter(std::string& error_msg) {
|
||||
mosis::MessageBus bus;
|
||||
|
||||
bool imageReceived = false;
|
||||
bool textReceived = false;
|
||||
|
||||
// Register with MIME filter
|
||||
bus.RegisterReceiver("image.viewer", {"view", {"image/png", "image/jpeg"}},
|
||||
[&](const mosis::Intent&) { imageReceived = true; });
|
||||
bus.RegisterReceiver("text.viewer", {"view", {"text/plain"}},
|
||||
[&](const mosis::Intent&) { textReceived = true; });
|
||||
|
||||
// Send image intent
|
||||
mosis::Intent imgIntent{"view", "image/png", "data", "sender"};
|
||||
std::string err;
|
||||
bus.Broadcast("sender", imgIntent, err);
|
||||
|
||||
EXPECT_TRUE(imageReceived);
|
||||
EXPECT_TRUE(!textReceived);
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### Test 5: Data Size Limit
|
||||
|
||||
```cpp
|
||||
bool Test_MessageBusDataLimit(std::string& error_msg) {
|
||||
mosis::MessageBus bus;
|
||||
|
||||
bus.RegisterReceiver("receiver", {"share", {}}, [](const mosis::Intent&) {});
|
||||
|
||||
// Create oversized data (> 1MB)
|
||||
std::string largeData(2 * 1024 * 1024, 'x');
|
||||
|
||||
mosis::Intent intent{"share", "text/plain", largeData, "sender"};
|
||||
std::string err;
|
||||
auto result = bus.Send("sender", "receiver", intent, err);
|
||||
|
||||
EXPECT_TRUE(result == mosis::MessageError::DataTooLarge);
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### Test 6: Blocked App Cannot Send
|
||||
|
||||
```cpp
|
||||
bool Test_MessageBusBlockedApp(std::string& error_msg) {
|
||||
mosis::MessageBus bus;
|
||||
|
||||
bus.RegisterReceiver("receiver", {"share", {}}, [](const mosis::Intent&) {});
|
||||
bus.BlockApp("bad.app");
|
||||
|
||||
mosis::Intent intent{"share", "text/plain", "Hello", "bad.app"};
|
||||
std::string err;
|
||||
auto result = bus.Send("bad.app", "receiver", intent, err);
|
||||
|
||||
EXPECT_TRUE(result == mosis::MessageError::SenderBlocked);
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### Test 7: Lua Integration
|
||||
|
||||
```cpp
|
||||
bool Test_MessageBusLuaIntegration(std::string& error_msg) {
|
||||
SandboxContext ctx = TestContext();
|
||||
LuaSandbox sandbox(ctx);
|
||||
|
||||
mosis::MessageBus bus;
|
||||
mosis::RegisterIntentsAPI(sandbox.GetState(), &bus, "test.app");
|
||||
|
||||
std::string script = R"lua(
|
||||
-- Test that intents global exists
|
||||
if not intents then
|
||||
error("intents global not found")
|
||||
end
|
||||
if not intents.on then
|
||||
error("intents.on not found")
|
||||
end
|
||||
if not intents.send then
|
||||
error("intents.send not found")
|
||||
end
|
||||
if not intents.broadcast then
|
||||
error("intents.broadcast not found")
|
||||
end
|
||||
if not intents.hasReceiver then
|
||||
error("intents.hasReceiver not found")
|
||||
end
|
||||
if not intents.getReceivers then
|
||||
error("intents.getReceivers not found")
|
||||
end
|
||||
if not intents.unregisterAll then
|
||||
error("intents.unregisterAll not found")
|
||||
end
|
||||
)lua";
|
||||
|
||||
bool ok = sandbox.LoadString(script, "intents_test");
|
||||
if (!ok) {
|
||||
error_msg = "Lua test failed: " + sandbox.GetLastError();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
All tests must pass:
|
||||
|
||||
- [x] `Test_MessageBusSendToReceiver` - Send reaches registered receiver
|
||||
- [x] `Test_MessageBusBlockUnregistered` - Unregistered action fails
|
||||
- [x] `Test_MessageBusBroadcast` - Broadcast reaches all matching receivers
|
||||
- [x] `Test_MessageBusMimeFilter` - MIME type filtering works
|
||||
- [x] `Test_MessageBusDataLimit` - Data size limit enforced
|
||||
- [x] `Test_MessageBusBlockedApp` - Blocked apps cannot send
|
||||
- [x] `Test_MessageBusLuaIntegration` - Lua API works
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Milestone 1 (LuaSandbox)
|
||||
- Milestone 2 (PermissionGate)
|
||||
- Milestone 3 (AuditLog)
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
### Security Considerations
|
||||
|
||||
1. **Kernel mediation**: All messages route through MessageBus, no direct app-to-app
|
||||
2. **Sender verification**: `from_app` is set by kernel, cannot be spoofed
|
||||
3. **Permission checks**: Sensitive actions require permissions
|
||||
4. **Size limits**: 1MB max to prevent resource exhaustion
|
||||
5. **App blocking**: Misbehaving apps can be blocked
|
||||
6. **Audit logging**: All inter-app communication is logged
|
||||
|
||||
### Privacy Features
|
||||
|
||||
1. **Opt-in receiving**: Apps must explicitly register for actions
|
||||
2. **MIME filtering**: Receivers can filter by content type
|
||||
3. **No discovery**: Apps cannot enumerate other apps (only check receivers)
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
After Milestone 18 passes:
|
||||
1. Milestone 19: Security Testing Suite
|
||||
Reference in New Issue
Block a user