chiark / gitweb /
alter game create table
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 22 Aug 2020 15:50:40 +0000 (16:50 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 22 Aug 2020 15:50:46 +0000 (16:50 +0100)
src/bin/otter.rs
src/cmdlistener.rs
src/commands.rs
src/gamestate.rs
src/global.rs
src/spec.rs

index b6ce9227fabb7d791f08215e309b9250a19ee076..eac8c41ec8b1db790255335c2ffe1ea9c723382c 100644 (file)
@@ -179,15 +179,15 @@ fn main() {
 }
 
 struct Conn {
-  mc: MgmtChannel,
+  chan: MgmtChannel,
 }
 
 impl Conn {
   #[throws(AE)]
   fn cmd(&mut self, cmd: &MgmtCommand) -> MgmtResponse {
     use MgmtResponse::*;
-    self.mc.write(&cmd).context("send command")?;
-    let resp = self.mc.read().context("read response")?;
+    self.chan.write(&cmd).context("send command")?;
+    let resp = self.chan.read().context("read response")?;
     match &resp {
       Fine{..} | GamesList{..} => { },
       AlterGame { error: None, .. } => { },
@@ -208,85 +208,93 @@ struct ConnForGame {
   pub conn: Conn,
   pub name: String,
   pub how: MgmtGameUpdateMode,
-};
+}
 impl Deref for ConnForGame {
   type Target = Conn;
-  fn deref(cg: &ConnForGame) -> Conn { &cg.conn }
+  fn deref(&self) -> &Conn { &self.conn }
 }
 impl DerefMut for ConnForGame {
-  fn deref_mut(cg: &mut ConnForGame) -> Conn { &mut cg.conn }
+  fn deref_mut(&mut self) -> &mut Conn { &mut self.conn }
 }
 
 impl ConnForGame {
   #[throws(AE)]
-  fn alter_game(&mut self, insns: Vec<MgmtGameInstructions>,
-                f: &mut dyn FnMut(&MgmtGameInstruction, &MgmtGameResult)
-                                  -> Result<AE>)
-                     -> Vec<MgmtGameResult>> {
+  fn alter_game(&mut self, insns: Vec<MgmtGameInstruction>,
+                f: &mut dyn FnMut(&MgmtGameResponse) -> Result<(),AE>)
+                -> Vec<MgmtGameResponse> {
+    let insns_len = insns.len();
     let cmd = MgmtCommand::AlterGame {
       name: self.name.clone(), how: self.how,
-      insns,
+      insns
     };
-    let results = match self.cmd(&cmd) {
-      MgmtResponse::AlterGame { error: None, results }
-      if results.len() == cmd.insns.len()
-      => results,
-      wat => anyhow!("unexpected AlterGame response: {:?} => {:?}",
-                     &cmd, &wat),
+    let responses = match self.cmd(&cmd)? {
+      MgmtResponse::AlterGame { error: None, responses }
+      if responses.len() == insns_len => {
+        responses
+      },
+      wat => Err(anyhow!("unexpected AlterGame response: {:?} => {:?}",
+                         &cmd, &wat))?,
     };
-    for (insn, result) in insns.iter.zip(results.iter()) {
-      f(insn,result)?;
+    for response in &responses {
+      f(response)?;
     }
-    results
+    responses
   }
 
-  #[throws(AE)]
-  fn game_state(&mut self) -> (GameState, HashMap<String,PlayerId>) {
-    let gs = self.alter_game(
-      vec![ MgmtGameInstruction::GetState { } ],
-      |..|Ok(())
+  fn get_players(&mut self) ->
+    Result<(PlayerMap, HashMap<String,PlayerId>),AE>
+  {
+    let mut players = self.alter_game(
+      vec![ MgmtGameInstruction::GetPlayers { } ],
+      &mut |_|Ok(())
     )?;
-    let gs = match gs {
-      &[ GameState { gs } ] => gs,
-      wat => anyhow!("GetGstate got {:?}", &wat);
+    let players = match players.pop() {
+      Some(MgmtGameResponse::Players(players)) => players,
+      wat => Err(anyhow!("GetGstate got {:?}", &wat))?,
     };
     let mut nick2id = HashMap::new();
-    for (player, pstate) in &gs.players {
+    for (player, pstate) in players.iter() {
+      use hash_map::Entry::*;
       match nick2id.entry(pstate.nick.clone()) {
-        Occupied(oe) => anyhow!("game has duplicate nick {:?}, {} {}",
-                                &nick, *oe.get(), player),
-        Vacant(ve) ve.insert(player),
-      }
+        Occupied(oe) => Err(anyhow!("game has duplicate nick {:?}, {} {}",
+                                    &pstate.nick, *oe.get(), player))?,
+        Vacant(ve) => ve.insert(player),
+      };
     }
-    (gs, nick2id)
+    Ok((players, nick2id))
   }
 }
 
 #[throws(E)]
-fn connect(ma: &MainOpts) -> MgmtChannel {
+fn connect(ma: &MainOpts) -> Conn {
   let unix = UnixStream::connect(SOCKET_PATH).context("connect to server")?;
-  let mut chan = MgmtChannel::new(unix)?;
+  let chan = MgmtChannel::new(unix)?;
+  let mut chan = Conn { chan };
   chan.cmd(&MgmtCommand::SetScope { scope: ma.scope.clone().unwrap() })?;
   chan
 }
 
-#[throws(E)]
-fn setup_table(game: &mut ConnForGame, spec: &TableSpec) {
+fn setup_table(chan: &mut ConnForGame, spec: &TableSpec) -> Result<(),AE> {
+  // xxx should delete old players
+
   // create missing players
   let (added_players,) = {
-    let (gs, nick2id) = chan.game_state()?;
+    let (_, nick2id) = chan.get_players()?;
 
-    let mut inss = vec![];
-    for psec in &spec.players {
-      if !nick2id.has_key(pspec.nick) {
-        insns.push(AddPlayer { nick: ps.nick });
+    let mut insns = vec![];
+    for pspec in &spec.players {
+      if !nick2id.contains_key(&pspec.nick) {
+        insns.push(MgmtGameInstruction::AddPlayer(PlayerState {
+          nick: pspec.nick.clone()
+        }));
       }
     }
     let mut added_players = HashSet::new();
-    chan.alter_game(name, insns, |insn,result| {
-      let player = match result {
-        MgmtGameInstruction::AddPlayer { player } => player,
-        _ => anyhow!("AddPlayer strange answer: {:?} => {:?}",&insn,&result),
+    chan.alter_game(insns, &mut |response| {
+      let player = match response {
+        &MgmtGameResponse::AddPlayer(player) => player,
+        _ => Err(anyhow!("AddPlayer strange answer {:?}",
+                         &response))?,
       };
       added_players.insert(player);
       Ok(())
@@ -297,44 +305,52 @@ fn setup_table(game: &mut ConnForGame, spec: &TableSpec) {
 
   // ensure players have access tokens
   {
-    let (gs, nick2id) = chan.game_state()?;
+    let (_, nick2id) = chan.get_players()?;
     let mut insns = vec![];
     let mut resetreport = vec![];
     let mut resetspecs = vec![];
     for pspec in &spec.players {
-      let player = nick2id.get(&pspec.nick)
-        .ok_or_else(anyhow!("player {:?} vanished or renamed!",
-                            &pspec.nick))?;
-      match pspec.access.token_mgi(player) {
-        Some(insn) => insns.push(insn),
-        None if added_players.contains(player) => {
-          resetreport.push(player);
-          resetspecs.push(player);
-        },
-        None => (),
+      let player = *nick2id.get(&pspec.nick)
+        .ok_or_else(||anyhow!("player {:?} vanished or renamed!",
+                              &pspec.nick))?;
+      if let Some(access) = &pspec.access {
+        match access.token_mgi(player) {
+          Some(insn) => insns.push(insn),
+          None if added_players.contains(&player) => {
+            resetreport.push(player);
+            resetspecs.push((pspec, access));
+          },
+          None => (),
+        }
       };
     }
     insns.push(MgmtGameInstruction::ResetPlayerAccesses {
       players: resetreport.clone(),
     });
-    insns.push(MgmtGameInstruction::ReporPlayerAccesses {
+    insns.push(MgmtGameInstruction::ReportPlayerAccesses {
       players: resetreport.clone(),
     });
-    let got_tokens = None;
-    chan.alter_game(insns, &mut |insn,result| {
-      if let PlayerAccessTokens { tokens } = result {
-        got_tokens = Some(tokens.clone()),
+    let mut got_tokens = None;
+    chan.alter_game(insns, &mut |response| {
+      if let MgmtGameResponse::PlayerAccessTokens { tokens } = response {
+        got_tokens = Some(tokens.clone());
       }
-    });
+      Ok(())
+    })?;
     let got_tokens = match got_tokens {
       Some(t) if t.len() == resetreport.len() => t,
-      wat => anyhow!("Did not get expected ReportPlayerAccesses! {:?}", &wat)?,
+      wat => Err(anyhow!("Did not get expected ReportPlayerAccesses! {:?}",
+                         &wat))?,
     };
-    for (pspec, ptokens) in resetspecs.iter().zip(got_tokens.iter()) {
-      pspec.access.deliver_tokens(&pspec, &ptokens)
-        .context("deliver tokens for nick={:?}", &pspec.nick)?;
+    for ((pspec, access), ptokens)
+      in resetspecs.iter().zip(got_tokens.iter()) {
+      access.deliver_tokens(&pspec, &ptokens)
+          .with_context(||format!("deliver tokens for nick={:?}",
+                                  &pspec.nick))?;
     }
   }
+
+  Ok(())
 }
 
 mod create_table {
@@ -373,19 +389,21 @@ mod create_table {
       <Result<_,AE>>::Ok(spec)
     })().with_context(|| args.file.to_owned()).context("read game spec")?;
 
+    let mut chan = connect(&ma)?;
+
     chan.cmd(&MgmtCommand::CreateGame {
       name: args.name.clone(), insns: vec![]
     })?;
 
-    let chan = ConnForGame {
+    let mut chan = ConnForGame {
       conn: chan,
       name: args.name.clone(),
       how: MgmtGameUpdateMode::Bulk,
     };
 
-    setup_table(&chan &spec.table)?;
+    setup_table(&mut chan, &spec)?;
 
-    eprintln!("CREATE-TABLE DID SETUP_TABLE NEEDS GAMESPEC", &ma, &args);
+    eprintln!("CREATE-TABLE DID SETUP_TABLE NEEDS GAMESPEC"); // xxx
   }
 
   inventory::submit!{Subcommand(
index 50d740e60ab732dc3c19e53d68af08468972dca8..f5d5abc61f21caa69fab50c2107cb532d2db21b7 100644 (file)
@@ -203,7 +203,7 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse {
     },
 
     CreateGame { name, insns } => {
-      let gs = GameState {
+      let gs = crate::gamestate::GameState {
         pieces : Default::default(),
         players : Default::default(),
         log : Default::default(),
@@ -339,18 +339,18 @@ fn execute_for_game(cs: &CommandStream, ig: &mut InstanceGuard,
                     mut insns: Vec<MgmtGameInstruction>,
                     how: MgmtGameUpdateMode) -> MgmtResponse {
   let mut uh = UpdateHandler::from_how(how);
-  let mut results = Vec::with_capacity(insns.len());
+  let mut responses = Vec::with_capacity(insns.len());
   let ok = (||{
     for insn in insns.drain(0..) {
-      let (upieces, ulogs, resp) = execute_game_insn(ig, insn)?;
+      let (upieces, ulogs, resp) = execute_game_insn(cs, ig, insn)?;
       uh.accumulate(ig, upieces, ulogs)?;
-      results.push(resp);
+      responses.push(resp);
     }
     uh.complete(cs,ig)?;
     Ok(None)
   })();
   MgmtResponse::AlterGame {
-    results,
+    responses,
     error: ok.unwrap_or_else(Some)
   }
 }
@@ -361,12 +361,13 @@ const XXX_DEFAULT_POSD : Pos = [5,5];
 const CREATE_PIECES_MAX : u32 = 300;
 
 #[throws(ME)]
-fn execute_game_insn(ig: &mut InstanceGuard, update: MgmtGameInstruction)
+fn execute_game_insn(cs: &CommandStream,
+                     ig: &mut InstanceGuard, update: MgmtGameInstruction)
                      -> (Vec<(PieceId,PieceUpdateOp<()>)>,
                          Vec<LogEntry>,
-                         MgmtGameResult) {
+                         MgmtGameResponse) {
   use MgmtGameInstruction::*;
-  use MgmtGameResult::*;
+  use MgmtGameResponse::*;
   match update {
     Noop { } => (vec![], vec![], Fine { }),
 
@@ -378,7 +379,7 @@ fn execute_game_insn(ig: &mut InstanceGuard, update: MgmtGameInstruction)
        vec![ LogEntry {
          html: format!("The facilitator added a player xxx"),
        } ],
-       MgmtGameResult::AddPlayer { player })
+       MgmtGameResponse::AddPlayer(player))
     },
 
     RemovePlayer(player) => {
@@ -386,6 +387,10 @@ fn execute_game_insn(ig: &mut InstanceGuard, update: MgmtGameInstruction)
       (vec![], vec![], Fine{})
     },
 
+    GetPlayers { } => {
+      let players = ig.gs.players.clone();
+      (vec![], vec![], Players(players))
+    },
 
     ResetPlayerAccesses { players } => {
       let tokens = ig.players_access_reset(&players)?
@@ -398,6 +403,19 @@ fn execute_game_insn(ig: &mut InstanceGuard, update: MgmtGameInstruction)
       (vec![], vec![], PlayerAccessTokens { tokens })
     }
 
+    SetFixedPlayerAccess { player, token } => {
+      let authorised : AuthorisedSatisfactory =
+        authorise_scope(cs, &ManagementScope::Server)?;
+      let authorised = match authorised.into_inner() {
+        ManagementScope::Server => Authorised::<RawToken>::authorise(),
+        _ => panic!(),
+      };
+      ig.player_access_register_fixed(
+        player, token, authorised
+      )?;
+      (vec![], vec![], Fine{})
+    }
+
     AddPiece(PiecesSpec{ pos,posd,count,face,info }) => {
       let gs = &mut ig.gs;
       let count = count.unwrap_or(1);
@@ -511,6 +529,8 @@ impl CommandListener {
 use authproofs::*;
 use authproofs::AuthorisationError;
 
+pub use authproofs::Authorised;
+
 mod authproofs {
   use crate::imports::*;
 
index 27c92574e6892bfbf6d4640641ed35d39a73c799..207be925eb8a4b7877108acfe071319d3a9c40f4 100644 (file)
@@ -17,9 +17,8 @@ pub enum MgmtCommand {
 pub enum MgmtResponse {
   Fine { },
   Error { error: MgmtError },
-  AlterGame { error: Option<MgmtError>, results: Vec<MgmtGameResult> },
+  AlterGame { error: Option<MgmtError>, responses: Vec<MgmtGameResponse> },
   GamesList { games: Vec<Arc<InstanceName>> },
-  GameState { gs: GameState },
 }
 
 #[derive(Debug,Serialize,Deserialize)]
@@ -29,25 +28,26 @@ pub enum MgmtGameInstruction {
   // todo: RemovePiece
   AddPlayer(PlayerState),
   RemovePlayer(PlayerId),
-  GetState { },
+  GetPlayers,
   ResetPlayerAccesses { players: Vec<PlayerId> },
   ReportPlayerAccesses { players: Vec<PlayerId> },
   SetFixedPlayerAccess { player: PlayerId, token: RawToken },
 }
 
 #[derive(Debug,Serialize,Deserialize)]
+pub enum MgmtGameResponse {
+  Fine { },
+  AddPlayer(PlayerId),
+  PlayerAccessTokens { tokens: Vec<Vec<RawToken>> },
+  Players(PlayerMap),
+}
+
+#[derive(Debug,Copy,Clone,Serialize,Deserialize)]
 pub enum MgmtGameUpdateMode {
   Online,
   Bulk,
 }
 
-#[derive(Debug,Serialize,Deserialize)]
-pub enum MgmtGameResult {
-  Fine { },
-  AddPlayer { player: PlayerId },
-  PlayerAccessTokens { tokens: Vec<Vec<RawToken>> },
-}
-
 #[derive(Debug,Clone,Error,Serialize,Deserialize)]
 pub enum MgmtError {
   ParseFailed(String),
index 8868962f03f796ccc5ebc91d3f1109d6984aab2a..35aa60517cb2d4afaf5ab75a2dfb854771a12f99 100644 (file)
@@ -58,6 +58,8 @@ pub struct PieceState {
   pub gen_before_lastclient : Generation,
 }
 
+pub type PlayerMap = DenseSlotMap<PlayerId,PlayerState>;
+
 #[derive(Debug,Clone,Serialize,Deserialize)]
 pub struct PlayerState {
   pub nick : String,
index 6cd882da4304709cb950c8531069f796247e49ce..6edd2df48462079de35de878a0a3a86ddb153e72 100644 (file)
@@ -48,7 +48,6 @@ pub struct Client {
   pub lastseen : Instant,
 }
 
-pub type PlayerMap = DenseSlotMap<PlayerId,PlayerState>;
 /* xxx
 #[derive(Serialize,Deserialize)]
 #[repr(transparent)]
@@ -419,15 +418,18 @@ impl InstanceGuard<'_> {
     Ok(())
   }
 
-  #[throws(OE)]
-  pub fn player_access_register_xxx(&mut self, token: RawToken, player: PlayerId) {
-    // xxx server has to not allow even facilitators to define tokens
-    // xxx this fn should become part of player_access_add
+  #[throws(MgmtError)]
+  pub fn player_access_register_fixed(&mut self,
+                                      player: PlayerId, token: RawToken,
+                                      _safe: Authorised<RawToken>
+  ) {
+    self.tokens_deregister_for_id(|id:PlayerId| id==player);
     let iad = InstanceAccessDetails {
       gref : self.gref.clone(),
       ident : player
     };
     self.token_register(token, iad);
+    self.save_access_now()?;
   }
 
   #[throws(MgmtError)]
@@ -439,7 +441,6 @@ impl InstanceGuard<'_> {
     for &player in players {
       self.c.g.gs.players.get(player).ok_or(MgmtError::PlayerNotFound)?;
     }
-    self.tokens_deregister_for_id(|id:PlayerId| players.contains(&id));
     self.save_access_now()?;
     let mut tokens = vec![];
     for &player in players {
@@ -840,7 +841,8 @@ pub fn xxx_global_setup() {
     let player = g.player_new(PlayerState {
       nick : nick.to_string(),
     })?;
-    g.player_access_register_xxx(RawToken(token.to_string()), player)?;
+    g.player_access_register_fixed(player, RawToken(token.to_string()),
+                                   Authorised::authorise())?;
   }
   g.save_access_now().unwrap();
 }
index 10a9aba3a077cd2c3f04dbac40dc48839f3ad47c..aed4fb6912497e7b8d5028b870184a17dfcc600e 100644 (file)
@@ -33,11 +33,11 @@ pub struct PiecesSpec {
 
 #[typetag::serde(tag="access")]
 pub trait PlayerAccessSpec : Debug {
-  fn token_mgi(&self, player: PlayerId) -> Option<MgmtGameInstructions> {
+  fn token_mgi(&self, _player: PlayerId) -> Option<MgmtGameInstruction> {
     None
   }
-  #[throws(AE)]
-  fn deliver_token(&self, ps: &PlayerSpec, tokens: &[RawToken]) { }
+  fn deliver_tokens(&self, ps: &PlayerSpec, tokens: &[RawToken])
+    -> Result<(),AE>;
 }
 
 #[derive(Debug,Serialize,Deserialize)]
@@ -45,22 +45,25 @@ struct FixedToken(RawToken);
 
 #[typetag::serde]
 impl PlayerAccessSpec for FixedToken {
-  fn token_mgi(&self, player: PlayerId) -> Option<MgmtGameInstructions> {
-    Some(MgmtGmmeInstruction::SetFixedPlayerAccess {
+  fn token_mgi(&self, player: PlayerId) -> Option<MgmtGameInstruction> {
+    Some(MgmtGameInstruction::SetFixedPlayerAccess {
       player,
       token: self.0.clone(),
     })
   }
+  #[throws(AE)]
+  fn deliver_tokens(&self, _ps: &PlayerSpec, _tokens: &[RawToken]) { }
 }
 
 #[derive(Debug,Serialize,Deserialize)]
 struct TokenOnStdout;
 
 #[typetag::serde]
-impl PlayerAccessSpec for FixedToken {
+impl PlayerAccessSpec for TokenOnStdout {
   #[throws(AE)]
-  fn deliver_tokens(&self, ps: &PlayerSpec, token: &[RawToken]) {
-                   -> Result<(),anyhow::Error> {
-    println!("access nick={:?} token={}", &ps.nick, token);
+  fn deliver_tokens(&self, ps: &PlayerSpec, tokens: &[RawToken]) {
+    for token in tokens {
+      println!("access nick={:?} token={}", &ps.nick, token.0);
+    }
   }
 }