From 5c178e440797f33e8ffee8a0fb0cea81dfad3921 Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Sat, 17 Oct 2020 20:21:36 +0100 Subject: [PATCH] wip new account etc. Signed-off-by: Ian Jackson --- src/accounts.rs | 31 +++++++-- src/api.rs | 4 +- src/cmdlistener.rs | 27 ++++---- src/commands.rs | 5 +- src/global.rs | 160 +++++++++++++++++++++++++++++---------------- src/spec.rs | 21 ++++-- src/sse.rs | 6 +- src/updates.rs | 4 +- 8 files changed, 169 insertions(+), 89 deletions(-) diff --git a/src/accounts.rs b/src/accounts.rs index 49a15b32..8f71b835 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -4,7 +4,8 @@ use crate::imports::*; -use parking_lot::{RwLock, const_rwlock}; +use parking_lot::{RwLock, const_rwlock, + MappedRwLockReadGuard, MappedRwLockWriteGuard}; pub type AccountName = ScopedName; @@ -17,7 +18,7 @@ pub enum AccountScope { type AS = AccountScope; -#[derive(Debug)] +#[derive(Debug,Clone)] #[derive(Eq,PartialEq,Ord,PartialOrd,Hash)] #[derive(DeserializeFromStr,SerializeDisplay)] pub struct ScopedName { @@ -79,8 +80,30 @@ impl FromStr for ScopedName { pub struct AccountRecord { pub nick: String, pub timezone: String, - pub access: Box, + pub access: Arc, + pub tokens_revealed: HashMap, +} + +#[derive(Copy,Clone,Debug,Ord,PartialOrd,Eq,PartialEq)] +pub struct TokenRevelation { + latest: Timestamp, + earliest: Timestamp, } static ACCOUNTS : RwLock>> - = const_rwlock_new(None); + = const_rwlock(None); + +impl AccountRecord { + fn lookup(account: AccountName) + -> Option> { + ACCOUNTS.read().map( + |accounts| accounts?.get(account) + ) + } + fn lookup_mut(account: AccountName) + -> Option> { + ACCOUNTS.write().map( + |accounts| accounts?.get(account) + ) + } +} diff --git a/src/api.rs b/src/api.rs index 3c99c8f5..8d8a5360 100644 --- a/src/api.rs +++ b/src/api.rs @@ -110,14 +110,14 @@ fn api_piece_op(form : Json>) cl.lastseen = Instant::now(); let player = cl.player; let gs = &mut g.gs; - let g_pieces = &g.pieces; + let ipieces = &g.ipieces; let _ = gs.players.byid(player)?; let lens = TransparentLens { }; let piece = lens.decode_visible_pieceid(form.piece, player); use ApiPieceOpError::*; match (||{ - let p = g_pieces.get(piece).ok_or(OnlineError::PieceGone)?; + let p = ipieces.get(piece).ok_or(OnlineError::PieceGone)?; let pc = gs.pieces.byid_mut(piece)?; let q_gen = form.gen; diff --git a/src/cmdlistener.rs b/src/cmdlistener.rs index bffcb9c5..e7241d27 100644 --- a/src/cmdlistener.rs +++ b/src/cmdlistener.rs @@ -54,10 +54,13 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse { match cmd { Noop => Fine, - SetScope(wanted_scope) => { + SetAccount(wanted_account) => { let authorised : AuthorisedSatisfactory = - authorise_scope(cs, &wanted_scope)?; - cs.scope = Some(authorised.into_inner()); + authorise_scope(cs, &wanted_account.scope)?; + cs.account = ScopedName { + scope: Some(authorised.into_inner()), + scoped_nmae: wanted_account.scoped_name, + }; Fine }, @@ -212,23 +215,22 @@ fn execute_game_insn(cs: &CommandStream, Resp::Info(info) }), - ResetPlayerAccesses { players } => { - let tokens = ig.players_access_reset(&players)? - .drain(0..).map(|token| vec![token]).collect(); + ResetPlayerAccess(player) => { + let token = ig.players_access_reset(player)?; (U{ pcs: vec![], log: vec![], raw: None }, - PlayerAccessTokens(tokens)) + PlayerAccessToken(token)) } - ReportPlayerAccesses { players } => { - let tokens = ig.players_access_report(&players)?; + RedeliverPlayerAccess(player) => { + let token = ig.players_access_redeliver(player)?; (U{ pcs: vec![], log: vec![], raw: None }, - PlayerAccessTokens(tokens)) + PlayerAccessToken(token)) }, - +/* SetFixedPlayerAccess { player, token } => { let authorised : AuthorisedSatisfactory = authorise_scope(cs, &AS::Server)?; @@ -244,6 +246,7 @@ fn execute_game_insn(cs: &CommandStream, raw: None}, Fine) } +*/ DeletePiece(piece) => { let modperm = ig.modify_pieces(); @@ -422,7 +425,7 @@ impl UpdateHandler { struct CommandStream<'d> { euid : Result, desc : &'d str, - scope : Option, + account : Option, chan : MgmtChannel, who: Who, } diff --git a/src/commands.rs b/src/commands.rs index ea347eff..fff1f6c2 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -71,9 +71,8 @@ pub enum MgmtGameInstruction { DeletePiece(PieceId), ResetPlayerAccess(PlayerId), + // xxx ^ prevent use of Fixed when not wanted RedeliverPlayerAccess(PlayerId), - SetFixedPlayerAccess { player: PlayerId, token: RawToken }, - // xxx ^ immpl should check that PlayerState mentions Fixed AddPlayer(MgmtPlayerDetails), UpdatePlayer(MgmtPlayerDetails), @@ -140,9 +139,11 @@ pub enum MgmtError { AuthorisationError, NoScope, AlreadyExists, + NickCollision, GameBeingDestroyed, GameNotFound, GameCorrupted, + AccountNotFound, PlayerNotFound, PieceNotFound, LimitExceeded, diff --git a/src/global.rs b/src/global.rs index a415481d..ef0fea8a 100644 --- a/src/global.rs +++ b/src/global.rs @@ -33,23 +33,23 @@ pub struct InstanceRef (Arc>); pub struct Instance { pub name : Arc, pub gs : GameState, - pub pieces : PiecesLoaded, + pub ipieces : PiecesLoaded, pub clients : DenseSlotMap, - pub players : SecondarySlotMap, + pub iplayers : SecondarySlotMap, pub tokens_players : TokenRegistry, pub tokens_clients : TokenRegistry, } pub struct PlayerRecord { pub u: PlayerUpdates, - pub st: PlayerState, + pub pst: PlayerState, } +#[derive(Debug,Serialize,Deserialize)] pub struct PlayerState { pub account: AccountName, pub nick: String, pub tz: Timezone, - pub tokens_revealed: Vec, } #[derive(Debug,Serialize,Deserialize)] @@ -195,12 +195,7 @@ pub struct InstanceContainer { struct InstanceSaveAccesses { pieces: PiecesLoadedRef, tokens_players: Vec<(RawTokenStr, PlayerId)>, - aplayers: SecondarySlotMap, -} - -#[derive(Debug,Default,Serialize,Deserialize)] -struct PlayerSaveAccess { - tz: Timezone, + aplayers: SecondarySlotMap, } display_as_debug!{InstanceLockError} @@ -267,9 +262,9 @@ impl Instance { let g = Instance { name : name.clone(), gs, - pieces : PiecesLoaded(Default::default()), + ipieces : PiecesLoaded(Default::default()), clients : Default::default(), - updates : Default::default(), + iplayers : Default::default(), tokens_players : Default::default(), tokens_clients : Default::default(), }; @@ -371,17 +366,30 @@ impl InstanceGuard<'_> { logentry: LogEntry) -> (PlayerId, LogEntry) { // saving is fallible, but we can't attempt to save unless // we have a thing to serialise with the player in it - if self.c.g.gs.players.values().any(|pl| pl.nick == newplayer.nick) { + if self.c.g.gs.players.values().any(|a| a == &newplayer.account) { Err(MgmtError::AlreadyExists)?; } - let player = self.c.g.gs.players.insert(newplayer); - self.save_game_now().map_err(|e|{ + if self.c.g.iplayers.values().any(|pl| pl.pst.nick == newplayer.nick) { + Err(MgmtError::NickCollision)?; + } + let player = self.c.g.gs.players.insert(newplayer.account.clone()); + let u = PlayerUpdates::new_begin(&self.c.g.gs).new(); + let record = PlayerRecord { u, pst: newplayer }; + self.c.g.iplayers.insert(player, record); + + (||{ + self.save_game_now()?; + self.save_access_now()?; + Ok(()) + })().map_err(|e|{ + self.c.g.iplayers.remove(player); self.c.g.gs.players.remove(player); + // Perhaps we leave the g-* file with this player recorded, + // but this will be ignored when we load. e })?; (||{ - let pu_bc = PlayerUpdates::new_begin(&self.c.g.gs); - self.c.g.updates.insert(player, pu_bc.new(tz)); + })(); // <- No ?, ensures that IEFE is infallible (barring panics) (player, logentry) } @@ -470,8 +478,9 @@ impl InstanceGuard<'_> { if remove { clients_to_remove.insert(k); } !remove }); - if let Some(mut updates) = self.updates.remove(oldplayer) { - updates. push(PreparedUpdate { + if let Some(PlayerRecord { u: mut updates, .. }) + = self.iplayers.remove(oldplayer) { + updates.push(PreparedUpdate { gen: self.c.g.gs.gen, when: Instant::now(), us : vec![ PreparedUpdateEntry::Error( @@ -497,6 +506,8 @@ impl InstanceGuard<'_> { player: PlayerId, token: RawToken, _safe: Authorised ) { + // xxx call this function when access changes + self.tokens_deregister_for_id(|id:PlayerId| id==player); let iad = InstanceAccessDetails { gref : self.gref.clone(), @@ -507,27 +518,47 @@ impl InstanceGuard<'_> { } #[throws(MgmtError)] - pub fn players_access_reset(&mut self, players: &[PlayerId]) - -> Vec { - // xxx disconnect everyone when token changes - for &player in players { - self.c.g.gs.players.get(player).ok_or(MgmtError::PlayerNotFound)?; - } - self.save_access_now()?; - let mut tokens = vec![]; - for &player in players { - let iad = InstanceAccessDetails { - gref : self.gref.clone(), - ident : player - }; - let token = RawToken::new_random(); - self.token_register(token.clone(), iad); - tokens.push(token); - } + pub fn player_access_reset(&mut self, player: PlayerId) + -> Option { + let pst = self.c.g.players.get(player) + .ok_or(MgmtError::PlayerNotFound)? + .pst; self.save_access_now()?; - // If the save fails, we don't return the token so no-one can use - // it. Therefore we don't need to bother deleting it. - tokens + + let access = { + let acct = AccountRecord::lookup_mut(&pst.account)? + .ok_or(MgmtError::AccountNotFound)?; + let access = acct.access; + let desc = access.describe_html(); + let now = Timestamp::now(); + access.entry(desc) + .or_insert(TokenRevelation { + latest: now, + earliest: now, + }) + .latest = now; + access.clone() + }; + + let token = access + .override_token() + .unwrap_or_else(||{ + RawToken::new_random() + // xxx disconnect everyone else + }); + + let iad = InstanceAccessDetails { + gref : self.gref.clone(), + ident : player + }; + self.token_register(token.clone(), iad); + + let report = AccessTokenReport { + url: format!("http://localhost:8000/{}", token.url), // xxx + }; + let report = access + .server_deliver(&pst, &report); + report.cloned() } #[throws(MgmtError)] @@ -692,9 +723,9 @@ impl InstanceGuard<'_> { .flatten() .collect() }; - let aplayers = s.c.g.updates.iter().map( - |(player, PlayerUpdates { tz, .. })| - (player, PlayerSaveAccess { tz: tz.clone() }) + let aplayers = s.c.g.iplayers.iter().map( + |(player, PlayerRecord { pst, .. })| + (player, pst) ).collect(); let isa = InstanceSaveAccesses { pieces, tokens_players, aplayers }; rmp_serde::encode::write_named(w, &isa) @@ -729,7 +760,7 @@ impl InstanceGuard<'_> { } } let InstanceSaveAccesses:: - { mut tokens_players, mut pieces, mut aplayers } + { mut tokens_players, mut ipieces, mut aplayers } = Self::load_something(&name, "a-") .or_else(|e| { if let InternalError::Anyhow(ae) = &e { @@ -742,31 +773,44 @@ impl InstanceGuard<'_> { Err(e) })?; - let gs = { - let mut gs : GameState = Self::load_something(&name, "g-")?; - for mut p in gs.pieces.values_mut() { - p.lastclient = Default::default(); - } - gs.pieces.0.retain(|k,_v| pieces.contains_key(k)); - pieces.retain(|k,_v| gs.pieces.0.contains_key(k)); + let mut gs : GameState = Self::load_something(&name, "g-")?; - gs - }; + fn discard_mismatches( + primary: &mut DenseSlotMap, + secondary: &mut SecondarySlotMap, + ) { + primary.retain(|k,_v| secondary.contains_key(k)); + secondary.retain(|k,_v| primary.contains_key(k)); + } - let mut updates : SecondarySlotMap<_,_> = Default::default(); + discard_mismatches(&mut gs.players, &mut aplayers); + discard_mismatches(&mut gs.pieces, &mut ipieces); + let pu_bc = PlayerUpdates::new_begin(&gs); - for player in gs.players.keys() { - let aplayer = aplayers.remove(player).unwrap_or_default(); - updates.insert(player, pu_bc.new(aplayer.tz)); + + let iplayers = { + let a = aplayers; + a.drain() + }.map(|pst| { + let u = pu_bc.new(); + PlayerRecord { u, pst } + }).collect(); + + for mut p in gs.pieces.values_mut() { + p.lastclient = Default::default(); + if let Some(held) = p.held { + if !gs.players.has_key(held) { p.held = None } + } } + let name = Arc::new(name); tokens_players.retain( |&(_,player)| gs.players.contains_key(player) ); let g = Instance { - gs, updates, - pieces: PiecesLoaded(pieces), + gs, iplayers, + ipieces: PiecesLoaded(ipieces), name: name.clone(), clients : Default::default(), tokens_clients : Default::default(), diff --git a/src/spec.rs b/src/spec.rs index cae19522..568b53de 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -10,11 +10,12 @@ use index_vec::{define_index_type,IndexVec}; use crate::gamestate::PieceSpec; use std::fmt::Debug; use std::collections::hash_set::HashSet; -use implementation::PlayerAccessSpec; use thiserror::Error; use crate::error::display_as_debug; use crate::accounts::AccountName; +pub use implementation::PlayerAccessSpec; + //---------- common types ---------- pub type Coord = isize; @@ -234,14 +235,18 @@ pub mod implementation { #[typetag::serde(tag="access")] pub trait PlayerAccessSpec : Debug { - fn client_mgi(&self, _player: &PlayerId) -> Option { + fn override_token(&self) -> Option<&RawToken> { + // xxx check this on setting access None } - fn server_deliver(&self, ps: &PlayerState, token: &AccessTokenReport) + fn client_mgi(&self, _player: PlayerId) -> Option { + None + } + fn server_deliver(&self, pst: &PlayerState, token: &AccessTokenReport) -> Result, AE> { Ok(None) } - fn client_deliver(&self, ps: &PlayerState, token: &AccessTokenReport) + fn client_deliver(&self, pst: &PlayerState, token: &AccessTokenReport) -> Result<(),AE> { panic!() } @@ -253,6 +258,9 @@ pub mod implementation { #[typetag::serde] impl PlayerAccessSpec for FixedToken { + fn override_token(&self) -> Option<&RawToken> { + Some(self.token) + } fn client_mgi(&self, player: PlayerId) -> Option { Some(Insn::SetFixedPlayerAccess { player, @@ -263,14 +271,15 @@ pub mod implementation { #[typetag::serde] impl PlayerAccessSpec for UrlOnStdout { - #[throws(AE)] fn client_mgi(&self, player: PlayerId) -> Option { Some(Insn::ReportPlayerAccesses(player)) } + #[throws(AE)] fn server_deliver(&self, ps: &PlayerState, token: &AccessTokenReport) - -> Result, AE> { + -> Option<&AccessTokenReport> { Some(token) } + #[throws(AE)] fn client_deliver(&self, ps: &PlayerState, token: &AccessTokenReport) { println!("access account={} nick={:?} url:\n{}", &ps.account, &ps.nick, token.url); diff --git a/src/sse.rs b/src/sse.rs index 4910ff90..1db533ef 100644 --- a/src/sse.rs +++ b/src/sse.rs @@ -218,11 +218,11 @@ pub fn content(iad : InstanceAccessDetails, gen: Generation) let _g = &mut g.gs; let cl = g.clients.byid(client)?; let player = cl.player; - trace!("updates content iad={:?} player={:?} cl={:?} updates={:?}", - &iad, &player, &cl, &g.updates); + trace!("updates content iad={:?} player={:?} cl={:?}", + &iad, &player, &cl); let gref = iad.gref.clone(); - let log = &g.updates.byid(player)?.read_log(); + let log = &g.iplayers.byid(player)?.u.read_log(); let to_send = match log.into_iter().rev() .find(|(_,update)| update.gen <= gen) { diff --git a/src/updates.rs b/src/updates.rs index 060487af..da095aaf 100644 --- a/src/updates.rs +++ b/src/updates.rs @@ -126,11 +126,11 @@ pub struct PlayerUpdatesBuildContext { } impl PlayerUpdatesBuildContext { - pub fn new(&self, tz: Timezone) -> PlayerUpdates { + pub fn new(&self) -> PlayerUpdates { let mut log = StableIndexVecDeque::with_capacity(50); log.push_back(self.u1.clone()); let cv = Default::default(); - PlayerUpdates { log, tz, cv } + PlayerUpdates { log, cv } } } -- 2.30.2