464 lines
12 KiB
Markdown
464 lines
12 KiB
Markdown
# 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
|