use crate::legion::{LegionError, LegionResult};
use legion_protocol::{AdminOperation, MemberOperation, BanOperation, KeyOperation, MemberRole,
ChannelMode, ChannelSettings, AdminResult, ChannelAdmin, Permission};
use phalanx_crypto::{Identity, PublicKey};
use std::collections::{HashMap, HashSet};
use std::time::{SystemTime, Duration};
use tokio::sync::RwLock;
use serde::{Serialize, Deserialize};
use tracing::{debug, info, warn, error};
pub struct AdvancedChannelManager {
channels: RwLock<HashMap<String, ManagedChannel>>,
server_settings: ServerChannelSettings,
operation_history: RwLock<Vec<ChannelOperation>>,
administrators: RwLock<HashMap<String, Vec<String>>>, scheduled_operations: RwLock<Vec<ScheduledOperation>>,
}
#[derive(Debug, Clone)]
struct ManagedChannel {
info: ChannelInfo,
settings: ChannelSettings,
members: HashMap<String, ChannelMember>,
bans: Vec<ChannelBan>,
stats: ChannelStats,
key_info: KeyInfo,
rate_limits: HashMap<String, RateState>,
activity_log: Vec<ActivityEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerChannelSettings {
pub max_channels_per_user: usize,
pub max_members_per_channel: usize,
pub default_key_rotation_interval: Duration,
pub default_history_retention: Duration,
pub max_bans_per_channel: usize,
pub enable_statistics: bool,
pub require_registration: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ChannelOperation {
timestamp: SystemTime,
channel: String,
operator: String,
operation: AdminOperation,
result: AdminResult,
context: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ScheduledOperation {
scheduled_time: SystemTime,
channel: String,
operation: AdminOperation,
priority: Priority,
recurring: Option<Duration>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialOrd, Ord, PartialEq, Eq)]
enum Priority {
Low,
Normal,
High,
Critical,
}
#[derive(Debug, Clone)]
struct RateState {
message_count: u32,
window_start: SystemTime,
burst_remaining: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ActivityEntry {
timestamp: SystemTime,
activity_type: ActivityType,
user_id: String,
details: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
enum ActivityType {
Join,
Leave,
Message,
TopicChange,
ModeChange,
Kick,
Ban,
KeyRotation,
AdminAction,
}
#[derive(Debug, Clone, Serialize)]
pub struct ExtendedChannelInfo {
pub basic: ChannelInfo,
pub administrators: Vec<String>,
pub recent_activity: Vec<ActivityEntry>,
pub health_metrics: ChannelHealth,
pub security_status: SecurityStatus,
}
#[derive(Debug, Clone, Serialize)]
pub struct ChannelHealth {
pub health_score: u8,
pub issues: Vec<HealthIssue>,
pub performance: PerformanceMetrics,
}
#[derive(Debug, Clone, Serialize)]
pub enum HealthIssue {
HighMessageRate,
ExcessiveBans,
OverdueKeyRotation,
HighMemberCount,
InactiveAdministrators,
}
#[derive(Debug, Clone, Serialize)]
pub struct PerformanceMetrics {
pub avg_message_time: Duration,
pub messages_per_second: f64,
pub encryption_ops_per_second: f64,
pub memory_usage: u64,
}
#[derive(Debug, Clone, Serialize)]
pub struct SecurityStatus {
pub encryption_active: bool,
pub key_rotation_current: bool,
pub all_members_verified: bool,
pub recent_security_events: Vec<SecurityEvent>,
}
#[derive(Debug, Clone, Serialize)]
pub enum SecurityEvent {
DecryptionFailure { user_id: String, timestamp: SystemTime },
SuspiciousActivity { description: String, timestamp: SystemTime },
UnauthorizedAccess { user_id: String, timestamp: SystemTime },
KeyRotationFailure { reason: String, timestamp: SystemTime },
}
impl Default for ServerChannelSettings {
fn default() -> Self {
Self {
max_channels_per_user: 20,
max_members_per_channel: 100,
default_key_rotation_interval: Duration::from_secs(86400), default_history_retention: Duration::from_secs(2592000), max_bans_per_channel: 50,
enable_statistics: true,
require_registration: false,
}
}
}
impl AdvancedChannelManager {
pub async fn new(server_settings: ServerChannelSettings) -> LegionResult<Self> {
Ok(Self {
channels: RwLock::new(HashMap::new()),
server_settings,
operation_history: RwLock::new(Vec::new()),
administrators: RwLock::new(HashMap::new()),
scheduled_operations: RwLock::new(Vec::new()),
})
}
pub async fn execute_admin_operation(
&self,
channel_name: &str,
operation: AdminOperation,
operator_id: &str,
operator_identity: &Identity,
) -> LegionResult<AdminResult> {
let start_time = SystemTime::now();
let operator_role = self.get_user_role(channel_name, operator_id).await?;
let operator_permissions = self.get_user_permissions(channel_name, operator_id).await?;
let admin = ChannelAdmin::new(
operator_id.to_string(),
operator_role,
operator_permissions,
);
let channel_settings = self.get_channel_settings(channel_name).await?;
if !admin.can_perform(&operation, &channel_settings) {
let result = AdminResult {
operation: operation.clone(),
success: false,
message: "Insufficient permissions".to_string(),
data: None,
timestamp: start_time,
};
self.log_operation(channel_name, operator_id, operation, result.clone()).await;
return Ok(result);
}
let result = match &operation {
AdminOperation::CreateChannel { channel, settings } => {
self.create_channel(channel, settings.clone(), operator_id).await
},
AdminOperation::SetTopic { channel, topic } => {
self.set_channel_topic(channel, topic, operator_id).await
},
AdminOperation::SetMode { channel, mode, enabled } => {
self.set_channel_mode(channel, mode.clone(), *enabled, operator_id).await
},
AdminOperation::MemberOperation { channel, target, operation: member_op } => {
self.execute_member_operation(channel, target, member_op.clone(), operator_id).await
},
AdminOperation::BanOperation { channel, target, operation: ban_op, duration } => {
self.execute_ban_operation(channel, target, ban_op.clone(), *duration, operator_id).await
},
AdminOperation::KeyOperation { channel, operation: key_op } => {
self.execute_key_operation(channel, key_op.clone(), operator_identity).await
},
};
let admin_result = result.unwrap_or_else(|e| AdminResult {
operation: operation.clone(),
success: false,
message: format!("Operation failed: {}", e),
data: None,
timestamp: start_time,
});
self.log_operation(channel_name, operator_id, operation.clone(), admin_result.clone()).await;
self.log_activity(
channel_name,
operator_id,
ActivityType::AdminAction,
Some(format!("Executed: {:?}", admin_result.operation)),
).await;
Ok(admin_result)
}
async fn create_channel(
&self,
channel_name: &str,
settings: ChannelSettings,
creator_id: &str,
) -> LegionResult<AdminResult> {
let mut channels = self.channels.write().await;
if channels.contains_key(channel_name) {
return Ok(AdminResult {
operation: AdminOperation::CreateChannel {
channel: channel_name.to_string(),
settings,
},
success: false,
message: "Channel already exists".to_string(),
data: None,
timestamp: SystemTime::now(),
});
}
let managed_channel = ManagedChannel {
info: ChannelInfo {
name: channel_name.to_string(),
settings: settings.clone(),
member_count: 1,
topic: settings.topic.clone(),
modes: settings.modes.clone(),
stats: ChannelStats::default(),
},
settings: settings.clone(),
members: {
let mut members = HashMap::new();
members.insert(creator_id.to_string(), ChannelMember {
user_id: creator_id.to_string(),
nickname: creator_id.to_string(), role: MemberRole::Founder,
joined_at: SystemTime::now(),
last_activity: SystemTime::now(),
public_key: None, custom_permissions: None,
is_online: true,
});
members
},
bans: Vec::new(),
stats: ChannelStats::default(),
key_info: KeyInfo {
key_version: 1,
created_at: SystemTime::now(),
rotation_schedule: Some(SystemTime::now() + self.server_settings.default_key_rotation_interval),
member_key_count: 1,
has_backup: false,
},
rate_limits: HashMap::new(),
activity_log: Vec::new(),
};
let channel_info = managed_channel.info.clone();
channels.insert(channel_name.to_string(), managed_channel);
let mut administrators = self.administrators.write().await;
administrators.insert(channel_name.to_string(), vec![creator_id.to_string()]);
info!("Created encrypted channel: {} by {}", channel_name, creator_id);
Ok(AdminResult {
operation: AdminOperation::CreateChannel {
channel: channel_name.to_string(),
settings,
},
success: true,
message: "Channel created successfully".to_string(),
data: Some(AdminData::ChannelInfo(channel_info)),
timestamp: SystemTime::now(),
})
}
async fn set_channel_topic(
&self,
channel_name: &str,
topic: &str,
operator_id: &str,
) -> LegionResult<AdminResult> {
let mut channels = self.channels.write().await;
if let Some(channel) = channels.get_mut(channel_name) {
channel.settings.topic = Some(topic.to_string());
channel.settings.topic_set_by = Some((operator_id.to_string(), SystemTime::now()));
channel.info.topic = Some(topic.to_string());
info!("Topic set for channel {}: {} by {}", channel_name, topic, operator_id);
Ok(AdminResult {
operation: AdminOperation::SetTopic {
channel: channel_name.to_string(),
topic: topic.to_string(),
},
success: true,
message: "Topic updated successfully".to_string(),
data: None,
timestamp: SystemTime::now(),
})
} else {
Ok(AdminResult {
operation: AdminOperation::SetTopic {
channel: channel_name.to_string(),
topic: topic.to_string(),
},
success: false,
message: "Channel not found".to_string(),
data: None,
timestamp: SystemTime::now(),
})
}
}
async fn set_channel_mode(
&self,
channel_name: &str,
mode: ChannelMode,
enabled: bool,
operator_id: &str,
) -> LegionResult<AdminResult> {
let mut channels = self.channels.write().await;
if let Some(channel) = channels.get_mut(channel_name) {
if enabled {
channel.settings.modes.insert(mode.clone());
channel.info.modes.insert(mode.clone());
} else {
channel.settings.modes.remove(&mode);
channel.info.modes.remove(&mode);
}
info!("Mode {:?} {} for channel {} by {}",
mode, if enabled { "enabled" } else { "disabled" }, channel_name, operator_id);
Ok(AdminResult {
operation: AdminOperation::SetMode {
channel: channel_name.to_string(),
mode,
enabled,
},
success: true,
message: format!("Mode {} successfully", if enabled { "enabled" } else { "disabled" }),
data: None,
timestamp: SystemTime::now(),
})
} else {
Ok(AdminResult {
operation: AdminOperation::SetMode {
channel: channel_name.to_string(),
mode,
enabled,
},
success: false,
message: "Channel not found".to_string(),
data: None,
timestamp: SystemTime::now(),
})
}
}
async fn execute_member_operation(
&self,
channel_name: &str,
target_user: &str,
operation: MemberOperation,
operator_id: &str,
) -> LegionResult<AdminResult> {
let mut channels = self.channels.write().await;
if let Some(channel) = channels.get_mut(channel_name) {
match &operation {
MemberOperation::Kick { reason } => {
if let Some(member) = channel.members.remove(target_user) {
channel.info.member_count = channel.members.len();
let reason_str = reason.clone().unwrap_or_else(|| "No reason given".to_string());
info!("User {} kicked from channel {} by {}: {}",
target_user, channel_name, operator_id, reason_str);
Ok(AdminResult {
operation: AdminOperation::MemberOperation {
channel: channel_name.to_string(),
target: target_user.to_string(),
operation: MemberOperation::Kick { reason: Some(reason_str.clone()) },
},
success: true,
message: format!("User kicked: {}", reason_str),
data: None,
timestamp: SystemTime::now(),
})
} else {
Ok(AdminResult {
operation: AdminOperation::MemberOperation {
channel: channel_name.to_string(),
target: target_user.to_string(),
operation: operation.clone(),
},
success: false,
message: "User not found in channel".to_string(),
data: None,
timestamp: SystemTime::now(),
})
}
},
MemberOperation::SetRole { role } => {
if let Some(member) = channel.members.get_mut(target_user) {
let old_role = member.role.clone();
member.role = role.clone();
info!("User {} role changed from {:?} to {:?} in channel {} by {}",
target_user, old_role, role, channel_name, operator_id);
Ok(AdminResult {
operation: AdminOperation::MemberOperation {
channel: channel_name.to_string(),
target: target_user.to_string(),
operation: MemberOperation::SetRole { role: role.clone() },
},
success: true,
message: format!("Role changed from {:?} to {:?}", old_role, role),
data: None,
timestamp: SystemTime::now(),
})
} else {
Ok(AdminResult {
operation: AdminOperation::MemberOperation {
channel: channel_name.to_string(),
target: target_user.to_string(),
operation: operation.clone(),
},
success: false,
message: "User not found in channel".to_string(),
data: None,
timestamp: SystemTime::now(),
})
}
},
_ => Ok(AdminResult {
operation: AdminOperation::MemberOperation {
channel: channel_name.to_string(),
target: target_user.to_string(),
operation,
},
success: false,
message: "Operation not yet implemented".to_string(),
data: None,
timestamp: SystemTime::now(),
}),
}
} else {
Ok(AdminResult {
operation: AdminOperation::MemberOperation {
channel: channel_name.to_string(),
target: target_user.to_string(),
operation,
},
success: false,
message: "Channel not found".to_string(),
data: None,
timestamp: SystemTime::now(),
})
}
}
async fn execute_ban_operation(
&self,
channel_name: &str,
target_pattern: &str,
operation: BanOperation,
duration: Option<SystemTime>,
operator_id: &str,
) -> LegionResult<AdminResult> {
let mut channels = self.channels.write().await;
if let Some(channel) = channels.get_mut(channel_name) {
match &operation {
BanOperation::Add { reason } => {
let ban = ChannelBan {
pattern: target_pattern.to_string(),
reason: reason.clone(),
set_by: operator_id.to_string(),
set_at: SystemTime::now(),
expires_at: duration,
ban_type: BanType::Full,
};
channel.bans.push(ban);
info!("Ban added for pattern {} in channel {} by {}: {}",
target_pattern, channel_name, operator_id,
reason.as_ref().map(|r| r.as_str()).unwrap_or("No reason"));
Ok(AdminResult {
operation: AdminOperation::BanOperation {
channel: channel_name.to_string(),
target: target_pattern.to_string(),
operation: BanOperation::Add { reason: reason.clone() },
duration,
},
success: true,
message: "Ban added successfully".to_string(),
data: None,
timestamp: SystemTime::now(),
})
},
BanOperation::Remove => {
let initial_len = channel.bans.len();
channel.bans.retain(|ban| ban.pattern != target_pattern);
if channel.bans.len() < initial_len {
info!("Ban removed for pattern {} in channel {} by {}",
target_pattern, channel_name, operator_id);
Ok(AdminResult {
operation: AdminOperation::BanOperation {
channel: channel_name.to_string(),
target: target_pattern.to_string(),
operation: BanOperation::Remove,
duration: None,
},
success: true,
message: "Ban removed successfully".to_string(),
data: None,
timestamp: SystemTime::now(),
})
} else {
Ok(AdminResult {
operation: AdminOperation::BanOperation {
channel: channel_name.to_string(),
target: target_pattern.to_string(),
operation: BanOperation::Remove,
duration: None,
},
success: false,
message: "Ban not found".to_string(),
data: None,
timestamp: SystemTime::now(),
})
}
},
BanOperation::List => {
let active_bans: Vec<ChannelBan> = channel.bans.iter()
.filter(|ban| ban.is_active())
.cloned()
.collect();
Ok(AdminResult {
operation: AdminOperation::BanOperation {
channel: channel_name.to_string(),
target: target_pattern.to_string(),
operation: BanOperation::List,
duration: None,
},
success: true,
message: format!("Found {} active bans", active_bans.len()),
data: Some(AdminData::BanList(active_bans)),
timestamp: SystemTime::now(),
})
},
BanOperation::Check => {
let is_banned = channel.bans.iter()
.any(|ban| ban.is_active() && ban.matches_pattern(target_pattern));
Ok(AdminResult {
operation: AdminOperation::BanOperation {
channel: channel_name.to_string(),
target: target_pattern.to_string(),
operation: BanOperation::Check,
duration: None,
},
success: true,
message: if is_banned { "Pattern is banned" } else { "Pattern is not banned" }.to_string(),
data: None,
timestamp: SystemTime::now(),
})
},
}
} else {
Ok(AdminResult {
operation: AdminOperation::BanOperation {
channel: channel_name.to_string(),
target: target_pattern.to_string(),
operation,
duration,
},
success: false,
message: "Channel not found".to_string(),
data: None,
timestamp: SystemTime::now(),
})
}
}
async fn execute_key_operation(
&self,
channel_name: &str,
operation: KeyOperation,
operator_identity: &Identity,
) -> LegionResult<AdminResult> {
let mut channels = self.channels.write().await;
if let Some(channel) = channels.get_mut(channel_name) {
match operation {
KeyOperation::Rotate => {
channel.key_info.key_version += 1;
channel.key_info.created_at = SystemTime::now();
channel.key_info.rotation_schedule = Some(
SystemTime::now() + self.server_settings.default_key_rotation_interval
);
info!("Key rotation performed for channel {} to version {}",
channel_name, channel.key_info.key_version);
Ok(AdminResult {
operation: AdminOperation::KeyOperation {
channel: channel_name.to_string(),
operation: KeyOperation::Rotate,
},
success: true,
message: format!("Keys rotated to version {}", channel.key_info.key_version),
data: Some(AdminData::KeyInfo(channel.key_info.clone())),
timestamp: SystemTime::now(),
})
},
_ => Ok(AdminResult {
operation: AdminOperation::KeyOperation {
channel: channel_name.to_string(),
operation,
},
success: false,
message: "Key operation not yet implemented".to_string(),
data: None,
timestamp: SystemTime::now(),
}),
}
} else {
Ok(AdminResult {
operation: AdminOperation::KeyOperation {
channel: channel_name.to_string(),
operation,
},
success: false,
message: "Channel not found".to_string(),
data: None,
timestamp: SystemTime::now(),
})
}
}
pub async fn get_extended_channel_info(&self, channel_name: &str) -> LegionResult<ExtendedChannelInfo> {
let channels = self.channels.read().await;
let administrators = self.administrators.read().await;
if let Some(channel) = channels.get(channel_name) {
let admins = administrators.get(channel_name).cloned().unwrap_or_default();
let recent_activity = channel.activity_log.iter()
.rev()
.take(10)
.cloned()
.collect();
Ok(ExtendedChannelInfo {
basic: channel.info.clone(),
administrators: admins,
recent_activity,
health_metrics: self.calculate_channel_health(channel).await,
security_status: self.assess_security_status(channel).await,
})
} else {
Err(LegionError::Channel(format!("Channel not found: {}", channel_name)))
}
}
async fn calculate_channel_health(&self, channel: &ManagedChannel) -> ChannelHealth {
let mut issues = Vec::new();
let mut health_score = 100u8;
if channel.stats.messages_today > 1000.0 {
issues.push(HealthIssue::HighMessageRate);
health_score = health_score.saturating_sub(10);
}
if channel.bans.len() > 20 {
issues.push(HealthIssue::ExcessiveBans);
health_score = health_score.saturating_sub(15);
}
if let Some(rotation_time) = channel.key_info.rotation_schedule {
if SystemTime::now() > rotation_time {
issues.push(HealthIssue::OverdueKeyRotation);
health_score = health_score.saturating_sub(25);
}
}
if channel.members.len() > 80 {
issues.push(HealthIssue::HighMemberCount);
health_score = health_score.saturating_sub(5);
}
ChannelHealth {
health_score,
issues,
performance: PerformanceMetrics {
avg_message_time: Duration::from_millis(50), messages_per_second: channel.stats.avg_messages_per_day / 86400.0,
encryption_ops_per_second: 10.0, memory_usage: 1024 * 1024, },
}
}
async fn assess_security_status(&self, channel: &ManagedChannel) -> SecurityStatus {
SecurityStatus {
encryption_active: true, key_rotation_current: channel.key_info.rotation_schedule
.map(|schedule| SystemTime::now() < schedule)
.unwrap_or(false),
all_members_verified: channel.members.values()
.all(|member| member.public_key.is_some()),
recent_security_events: Vec::new(), }
}
async fn log_operation(
&self,
channel_name: &str,
operator_id: &str,
operation: AdminOperation,
result: AdminResult,
) {
let mut history = self.operation_history.write().await;
history.push(ChannelOperation {
timestamp: SystemTime::now(),
channel: channel_name.to_string(),
operator: operator_id.to_string(),
operation,
result,
context: None,
});
let history_len = history.len();
if history_len > 1000 {
history.drain(..history_len - 1000);
}
}
async fn log_activity(
&self,
channel_name: &str,
user_id: &str,
activity_type: ActivityType,
details: Option<String>,
) {
let mut channels = self.channels.write().await;
if let Some(channel) = channels.get_mut(channel_name) {
channel.activity_log.push(ActivityEntry {
timestamp: SystemTime::now(),
activity_type,
user_id: user_id.to_string(),
details,
});
if channel.activity_log.len() > 100 {
channel.activity_log.drain(..channel.activity_log.len() - 100);
}
}
}
async fn get_user_role(&self, channel_name: &str, user_id: &str) -> LegionResult<MemberRole> {
let channels = self.channels.read().await;
if let Some(channel) = channels.get(channel_name) {
if let Some(member) = channel.members.get(user_id) {
Ok(member.role.clone())
} else {
Err(LegionError::Member(format!("User not found in channel: {}", user_id)))
}
} else {
Err(LegionError::Channel(format!("Channel not found: {}", channel_name)))
}
}
async fn get_user_permissions(&self, channel_name: &str, user_id: &str) -> LegionResult<HashSet<Permission>> {
let channels = self.channels.read().await;
if let Some(channel) = channels.get(channel_name) {
if let Some(member) = channel.members.get(user_id) {
Ok(member.custom_permissions.clone().unwrap_or_default())
} else {
Err(LegionError::Member(format!("User not found in channel: {}", user_id)))
}
} else {
Err(LegionError::Channel(format!("Channel not found: {}", channel_name)))
}
}
async fn get_channel_settings(&self, channel_name: &str) -> LegionResult<ChannelSettings> {
let channels = self.channels.read().await;
if let Some(channel) = channels.get(channel_name) {
Ok(channel.settings.clone())
} else {
Err(LegionError::Channel(format!("Channel not found: {}", channel_name)))
}
}
pub async fn process_scheduled_operations(&self) -> LegionResult<usize> {
let mut scheduled = self.scheduled_operations.write().await;
let now = SystemTime::now();
let mut processed = 0;
let mut remaining_operations = Vec::new();
for operation in scheduled.drain(..) {
if operation.scheduled_time <= now {
info!("Processing scheduled operation for channel {}: {:?}",
operation.channel, operation.operation);
processed += 1;
if let Some(interval) = operation.recurring {
let mut new_operation = operation;
new_operation.scheduled_time = now + interval;
remaining_operations.push(new_operation);
}
} else {
remaining_operations.push(operation);
}
}
*scheduled = remaining_operations;
Ok(processed)
}
}
use legion_protocol::admin::{ChannelInfo, ChannelStats, ChannelMember, ChannelBan, BanType, KeyInfo, AdminData};
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_channel_creation() {
let settings = ServerChannelSettings::default();
let manager = AdvancedChannelManager::new(settings).await.unwrap();
let identity = phalanx_crypto::Identity::generate();
let result = manager.execute_admin_operation(
"!test",
AdminOperation::CreateChannel {
channel: "!test".to_string(),
settings: ChannelSettings::default(),
},
"creator",
&identity,
).await.unwrap();
assert!(result.success);
}
#[tokio::test]
async fn test_topic_setting() {
let settings = ServerChannelSettings::default();
let manager = AdvancedChannelManager::new(settings).await.unwrap();
let identity = phalanx_crypto::Identity::generate();
let _create_result = manager.execute_admin_operation(
"!test",
AdminOperation::CreateChannel {
channel: "!test".to_string(),
settings: ChannelSettings::default(),
},
"creator",
&identity,
).await.unwrap();
let result = manager.execute_admin_operation(
"!test",
AdminOperation::SetTopic {
channel: "!test".to_string(),
topic: "Test channel topic".to_string(),
},
"creator",
&identity,
).await.unwrap();
assert!(result.success);
}
}