chiark / gitweb /
wip new account etc.
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 17 Oct 2020 19:21:36 +0000 (20:21 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 17 Oct 2020 19:21:36 +0000 (20:21 +0100)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
src/accounts.rs
src/api.rs
src/cmdlistener.rs
src/commands.rs
src/global.rs
src/spec.rs
src/sse.rs
src/updates.rs

index 49a15b32f6ae8d5f670bfc4750cb24c1795738db..8f71b8352a274e20b564540714fdb792454d79b9 100644 (file)
@@ -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<dyn PlayerAccessSpec>,
+  pub access: Arc<dyn PlayerAccessSpec>,
+  pub tokens_revealed: HashMap<Html, TokenRevelation>,
+}
+
+#[derive(Copy,Clone,Debug,Ord,PartialOrd,Eq,PartialEq)]
+pub struct TokenRevelation {
+  latest: Timestamp,
+  earliest: Timestamp,
 }
 
 static ACCOUNTS : RwLock<Option<HashMap<AccountName, AccountRecord>>>
-  = const_rwlock_new(None);
+  = const_rwlock(None);
+
+impl AccountRecord {
+  fn lookup(account: AccountName)
+            -> Option<MappedRwLockReadGuard<'static, AccountRecord>> {
+    ACCOUNTS.read().map(
+      |accounts| accounts?.get(account)
+    )
+  }
+  fn lookup_mut(account: AccountName)
+            -> Option<MappedRwLockWriteGuard<'static, AccountRecord>> {
+    ACCOUNTS.write().map(
+      |accounts| accounts?.get(account)
+    )
+  }
+}
index 3c99c8f5aabc3b665ae0a725741e60d19f40c2f9..8d8a53604c37eb1949c16143bf01d026b6c4d26b 100644 (file)
@@ -110,14 +110,14 @@ fn api_piece_op<O: ApiPieceOp>(form : Json<ApiPiece<O>>)
   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;
index bffcb9c5d13e79756ddb16a6c4206a57fc6054cb..e7241d2740a5451abf62891edee336c1b9e49be3 100644 (file)
@@ -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<Uid, ConnectionEuidDiscoverEerror>,
   desc : &'d str,
-  scope : Option<AccountScope>,
+  account : Option<AccountName>,
   chan : MgmtChannel,
   who: Who,
 }
index ea347effa08a553f2bd7e79604f443c628014788..fff1f6c28f3f18ec577713e803c70166d0161eb3 100644 (file)
@@ -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,
index a415481de662d939dc090b6b3732e639a5c1a697..ef0fea8a7a472bf3aab990f7df5e9e9cbf1b91bf 100644 (file)
@@ -33,23 +33,23 @@ pub struct InstanceRef (Arc<Mutex<InstanceContainer>>);
 pub struct Instance {
   pub name : Arc<InstanceName>,
   pub gs : GameState,
-  pub pieces : PiecesLoaded,
+  pub ipieces : PiecesLoaded,
   pub clients : DenseSlotMap<ClientId,Client>,
-  pub players : SecondarySlotMap<PlayerId, PlayerRecord>,
+  pub iplayers : SecondarySlotMap<PlayerId, PlayerRecord>,
   pub tokens_players : TokenRegistry<PlayerId>,
   pub tokens_clients : TokenRegistry<ClientId>,
 }
 
 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<String>,
 }
 
 #[derive(Debug,Serialize,Deserialize)]
@@ -195,12 +195,7 @@ pub struct InstanceContainer {
 struct InstanceSaveAccesses<RawTokenStr, PiecesLoadedRef> {
   pieces: PiecesLoadedRef,
   tokens_players: Vec<(RawTokenStr, PlayerId)>,
-  aplayers: SecondarySlotMap<PlayerId, PlayerSaveAccess>,
-}
-
-#[derive(Debug,Default,Serialize,Deserialize)]
-struct PlayerSaveAccess {
-  tz: Timezone,
+  aplayers: SecondarySlotMap<PlayerId, PlayerState>,
 }
 
 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<RawToken>
   ) {
+    // 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<RawToken> {
-      // 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<AccessTokenReport> {
+    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::<String,ActualPiecesLoaded>
-    { 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<K:slotmap::Key, V1, V2>(
+      primary:   &mut DenseSlotMap<K, V1>,
+      secondary: &mut SecondarySlotMap<K, V2>,
+    ) {
+      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(),
index cae19522ad08fac52e05a709a0baf0af95d73cb6..568b53de6b4c775b3591e09beca0d73e1e1904a0 100644 (file)
@@ -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<MgmtGameInstruction> {
+    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<MgmtGameInstruction> {
+      None
+    }
+    fn server_deliver(&self, pst: &PlayerState, token: &AccessTokenReport)
                       -> Result<Option<&AccessTokenReport>, 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<MgmtGameInstruction> {
       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<MgmtGameInstruction> {
       Some(Insn::ReportPlayerAccesses(player))
     }
+    #[throws(AE)]
     fn server_deliver(&self, ps: &PlayerState, token: &AccessTokenReport)
-                      -> Result<Option<&AccessTokenReport>, 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);
index 4910ff900e413044ef6c3782306b29e8be1c40e7..1db533ef2d1316ae835982fb2e20b77f8e36fafe 100644 (file)
@@ -218,11 +218,11 @@ pub fn content(iad : InstanceAccessDetails<ClientId>, 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) {
index 060487af80055fa847c8df6c25b3fc174acb84e1..da095aaf8399353c5887dbc57a3292dc2b16d9eb 100644 (file)
@@ -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 }
   }
 }