#[derive(Copy,Clone,Debug,Ord,PartialOrd,Eq,PartialEq)]
pub struct TokenRevelation {
- latest: Timestamp,
- earliest: Timestamp,
+ pub latest: Timestamp,
+ pub earliest: Timestamp,
}
static ACCOUNTS : RwLock<Option<HashMap<AccountName, AccountRecord>>>
= const_rwlock(None);
+// xxx load, incl reveleation expiry
+// xxx periodic token reveleation expiry
+
+pub fn save_accounts_now() -> Result<(), InternalError> {
+ panic!("xxx")
+}
+
impl AccountRecord {
- fn lookup(account: AccountName)
+ pub fn lookup(account: &AccountName)
-> Option<MappedRwLockReadGuard<'static, AccountRecord>> {
ACCOUNTS.read().map(
|accounts| accounts?.get(account)
)
}
- fn lookup_mut(account: AccountName)
+ pub fn lookup_mut_caller_must_save(account: &AccountName)
-> Option<MappedRwLockWriteGuard<'static, AccountRecord>> {
ACCOUNTS.write().map(
|accounts| accounts?.get(account)
)
}
+ pub fn with_entry_mut<T, F>(account: &AccountName, f: F)
+ -> Result<T, (InternalError, T)>
+ where F: FnOnce(Option<&mut AccountRecord>) -> T
+ {
+ let entry = AccountRecord::lookup_mut_caller_must_save(account);
+ let output = f(*entry);
+ let ok = if entry.is_some() { save_accounts_now() } else { Ok(()) };
+ match ok {
+ Ok(()) => Ok(output),
+ Err(e) => Err((e, output))
+ }
+ }
+
+ pub fn expire_tokens_revealed(&mut self) {
+ panic!("xxx")
+ }
}
op : O,
}
+struct ApiPieceOpArgs<'a> {
+ gs: &'a mut GameState,
+ player: PlayerId,
+ pst: &'a PlayerState,
+ piece: PieceId,
+ p: &'a dyn Piece,
+ iplayers: &'a SecondarySlotMap<PlayerId, PlayerRecord>,
+ lens: &'a dyn Lens /* used for LogEntry and PieceId but not Pos */
+}
+
trait ApiPieceOp : Debug {
#[throws(ApiPieceOpError)]
- fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId,
- p: &dyn Piece,
- lens: &dyn Lens /* used for LogEntry and PieceId but not Pos */)
- -> PieceUpdateFromOp;
+ fn op(&self, a: ApiPieceOpArgs) -> PieceUpdateFromOp;
#[throws(OnlineError)]
fn check_held(&self, pc: &PieceState, player: PlayerId) {
let player = cl.player;
let gs = &mut g.gs;
let ipieces = &g.ipieces;
+ let iplayers = &g.iplayers;
+ let pst = &iplayers.byid(player)?.pst;
let _ = gs.players.byid(player)?;
let lens = TransparentLens { };
let piece = lens.decode_visible_pieceid(form.piece, player);
if u_gen > q_gen { throw!(PieceOpError::Conflict) }
form.op.check_held(pc,player)?;
- let (wrc, update, logents) = form.op.op(gs,player,piece,p.as_ref(),&lens)?;
+ let (wrc, update, logents) =
+ form.op.op(ApiPieceOpArgs {
+ gs, player, pst, piece, iplayers,
+ p: p.as_ref(),
+ lens: &lens,
+ })?;
Ok::<_,ApiPieceOpError>((wrc, update, logents))
})() {
Err(ReportViaUpdate(poe)) => {
}
impl ApiPieceOp for ApiPieceGrab {
#[throws(ApiPieceOpError)]
- fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId,
- p: &dyn Piece, lens: &dyn Lens) -> PieceUpdateFromOp {
+ fn op(&self, a: ApiPieceOpArgs) -> PieceUpdateFromOp {
+ let ApiPieceOpArgs { gs,player,pst,piece,p,lens, .. } = a;
let pl = gs.players.byid(player)?;
let pc = gs.pieces.byid_mut(piece)?;
let logent = LogEntry {
html : Html(format!("{} grasped {}",
- &htmlescape::encode_minimal(&pl.nick),
+ &htmlescape::encode_minimal(&pst.nick),
p.describe_pri(&lens.log_pri(piece, pc)).0)),
};
fn check_held(&self, _pc: &PieceState, _player: PlayerId) { }
#[throws(ApiPieceOpError)]
- fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId,
- p: &dyn Piece, lens: &dyn Lens) -> PieceUpdateFromOp {
+ fn op(&self, a: ApiPieceOpArgs) -> PieceUpdateFromOp {
+ let ApiPieceOpArgs { gs,player,pst,piece,p,lens,iplayers, .. } = a;
let pl = gs.players.byid(player)?;
let pc = gs.pieces.byid_mut(piece)?;
let pcs = p.describe_pri(&lens.log_pri(piece, pc)).0;
let was = pc.held;
pc.held = Some(player);
- let was = was.and_then(|p| gs.players.get(p));
+ let was = was.and_then(|p| iplayers.get(p));
let update = PieceUpdateOp::Modify(());
- let pls = &htmlescape::encode_minimal(&pl.nick);
+ let pls = &htmlescape::encode_minimal(&pst.nick);
let logent = LogEntry { html : Html(match was {
Some(was) => format!("{} wrested {} from {}", pls, pcs,
- &htmlescape::encode_minimal(&was.nick)),
+ &htmlescape::encode_minimal(&was.pst.nick)),
None => format!("{} wrested {}", pls, pcs),
})};
}
impl ApiPieceOp for ApiPieceUngrab {
#[throws(ApiPieceOpError)]
- fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId,
- p: &dyn Piece, lens: &dyn Lens) -> PieceUpdateFromOp {
+ fn op(&self, a: ApiPieceOpArgs) -> PieceUpdateFromOp {
+ let ApiPieceOpArgs { gs,player,pst,piece,p,lens, .. } = a;
let pl = gs.players.byid(player).unwrap();
let pc = gs.pieces.byid_mut(piece).unwrap();
let logent = LogEntry {
html : Html(format!("{} released {}",
- &htmlescape::encode_minimal(&pl.nick),
+ &htmlescape::encode_minimal(&pst.nick),
p.describe_pri(&lens.log_pri(piece, pc)).0)),
};
}
impl ApiPieceOp for ApiPieceRaise {
#[throws(ApiPieceOpError)]
- fn op(&self, gs: &mut GameState, _: PlayerId, piece: PieceId,
- _p: &dyn Piece, _: &dyn Lens) -> PieceUpdateFromOp {
+ fn op(&self, a: ApiPieceOpArgs) -> PieceUpdateFromOp {
+ let ApiPieceOpArgs { gs,player,pst,piece,p,lens, .. } = a;
let pc = gs.pieces.byid_mut(piece).unwrap();
pc.zlevel = ZLevel { z : self.z.clone(), zg : gs.gen };
let update = PieceUpdateOp::SetZLevel(());
}
impl ApiPieceOp for ApiPieceMove {
#[throws(ApiPieceOpError)]
- fn op(&self, gs: &mut GameState, _: PlayerId, piece: PieceId,
- _p: &dyn Piece, _lens: &dyn Lens) -> PieceUpdateFromOp {
+ fn op(&self, a: ApiPieceOpArgs) -> PieceUpdateFromOp {
+ let ApiPieceOpArgs { gs,player,pst,piece,p,lens, .. } = a;
let pc = gs.pieces.byid_mut(piece).unwrap();
let (pos, clamped) = self.0.clamped(gs.table_size);
let logents = vec![];
}
impl ApiPieceOp for ApiPiecePin {
#[throws(ApiPieceOpError)]
- fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId,
- p: &dyn Piece, lens: &dyn Lens) -> PieceUpdateFromOp {
+ fn op(&self, a: ApiPieceOpArgs) -> PieceUpdateFromOp {
+ let ApiPieceOpArgs { gs,player,pst,piece,p,lens, .. } = a;
let pc = gs.pieces.byid_mut(piece).unwrap();
let pl = gs.players.byid(player).unwrap();
pc.pinned = self.0;
let update = PieceUpdateOp::Modify(());
let logents = vec![ LogEntry { html: Html(format!(
"{} {} {}",
- &htmlescape::encode_minimal(&pl.nick),
+ &htmlescape::encode_minimal(&pst.nick),
if pc.pinned { "pinned" } else { "unpinned" },
p.describe_pri(&lens.log_pri(piece, pc)).0
))}];
}
impl ApiPieceOp for ApiPieceUo {
#[throws(ApiPieceOpError)]
- fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId,
- p: &dyn Piece, lens: &dyn Lens) -> PieceUpdateFromOp {
+ fn op(&self, a: ApiPieceOpArgs) -> PieceUpdateFromOp {
+ let ApiPieceOpArgs { gs,player,pst,piece,p,lens, .. } = a;
'_normal_global_ops__not_loop: loop {
let pc = gs.pieces.byid_mut(piece)?;
let pl = gs.players.byid(player)?;
PieceUpdateOp::Modify(()),
vec![ LogEntry { html: Html(format!(
"{} flipped {}",
- &htmlescape::encode_minimal(&pl.nick),
+ &htmlescape::encode_minimal(&pst.nick),
p.describe_pri(&lens.log_pri(piece, pc)).0
)) }])
},
PlayerAccessToken(Option<AccessTokenReport>),
}
-#[derive(Debug,Serialize,Deserialize)]
+#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct AccessTokenReport {
pub url: String,
}
ZCoordinateOverflow(#[from] zcoord::Overflow),
BadGlob { pat: String, msg: String },
BadSpec(#[from] SpecError),
+ TokenDeliveryFailed(#[from] TokenDeliveryError),
}
impl Display for MgmtError {
#[throws(fmt::Error)]
use MgmtError::*;
match self {
ServerFailure(s) => write!(f, "ServerFailure: {}", &s)?,
+ TokenDeliveryFailed(tde) =>
+ write!(f, "access token delivery failed: {}", &tde)?,
_ => <Self as Debug>::fmt(self,f)?,
}
}
Anyhow(#[from] anyhow::Error),
}
+#[derive(Error,Debug)]
+pub enum TokenDeliveryError {
+}
+
impl From<InternalError> for SpecError {
fn from(ie: InternalError) -> SpecError {
SpecError::InternalError(format!("{:?}",ie))
visible_slotmap_key!{ VisiblePieceId('.') }
-#[derive(Clone,Serialize,Deserialize,Eq,Ord,PartialEq,PartialOrd)]
+#[derive(Clone,Serialize,Deserialize,Hash,Eq,Ord,PartialEq,PartialOrd)]
#[serde(transparent)]
pub struct Html (pub String);
}
impl Html {
+ // todo convert to display_as but I need to write display_as::typed::Is
pub fn lit(s: &str) -> Self { Html(s.to_owned()) }
+ pub fn from_txt(s: &str) -> Self {
+ Html(htmlescape::encode_minimal(&s))
+ }
}
impl Debug for Html {
pub pst: PlayerState,
}
-#[derive(Debug,Serialize,Deserialize)]
+#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct PlayerState {
pub account: AccountName,
pub nick: String,
#[derive(Debug,Default,Serialize,Deserialize)]
struct InstanceSaveAccesses<RawTokenStr, PiecesLoadedRef> {
- pieces: PiecesLoadedRef,
+ ipieces: PiecesLoadedRef,
tokens_players: Vec<(RawTokenStr, PlayerId)>,
aplayers: SecondarySlotMap<PlayerId, PlayerState>,
}
(||{
self.save_game_now()?;
self.save_access_now()?;
- Ok(())
+ Ok::<_,InternalError>(())
})().map_err(|e|{
self.c.g.iplayers.remove(player);
self.c.g.gs.players.remove(player);
// #[throws(ServerFailure)]
// https://github.com/withoutboats/fehler/issues/62
pub fn player_remove(&mut self, oldplayer: PlayerId)
- -> Result<Option<PlayerState>,InternalError> {
+ -> Result<(Option<AccountName>, Option<PlayerState>),
+ InternalError> {
// We have to filter this player out of everything
// Then save
// Then send updates
// We make a copy so if the save fails, we can put everything back
let mut players = self.c.g.gs.players.clone();
- let old_data = players.remove(oldplayer);
+ let old_account = players.remove(oldplayer);
// New state
let mut gs = GameState {
// point of no return
mem::drop(undo);
- (||{
+ let old_pst = (||{
for &piece in &updated_pieces {
(||Some({
self.c.g.gs.pieces.get_mut(piece)?.gen = self.c.g.gs.gen;
if remove { clients_to_remove.insert(k); }
!remove
});
- if let Some(PlayerRecord { u: mut updates, .. })
- = self.iplayers.remove(oldplayer) {
+ let pst = if let Some(PlayerRecord { u: mut updates, pst })
+ = self.iplayers.remove(oldplayer)
+ {
updates.push(PreparedUpdate {
gen: self.c.g.gs.gen,
when: Instant::now(),
ErrorSignaledViaUpdate::PlayerRemoved
)],
});
- }
+ Some(pst)
+ } else {
+ None
+ };
self.tokens_deregister_for_id(|id:PlayerId| id==oldplayer);
self.tokens_deregister_for_id(|id| clients_to_remove.contains(&id));
self.save_access_now().unwrap_or_else(
"trouble garbage collecting accesses for deleted player: {:?}",
&e)
);
+ pst
})(); // <- No ?, ensures that IEFE is infallible (barring panics)
- Ok(old_data)
+ Ok((old_account, old_pst))
}
#[throws(MgmtError)]
player: PlayerId, token: RawToken,
_safe: Authorised<RawToken>
) {
- // xxx call this function when access changes
-
+ // xxx get rid of this or something ?
self.tokens_deregister_for_id(|id:PlayerId| id==player);
let iad = InstanceAccessDetails {
gref : self.gref.clone(),
#[throws(MgmtError)]
pub fn player_access_reset(&mut self, player: PlayerId)
-> Option<AccessTokenReport> {
- let pst = self.c.g.players.get(player)
+ // xxx call this function when access changes
+
+ let pst = self.c.g.iplayers.get(player)
.ok_or(MgmtError::PlayerNotFound)?
.pst;
self.save_access_now()?;
- let access = {
- let acct = AccountRecord::lookup_mut(&pst.account)?
- .ok_or(MgmtError::AccountNotFound)?;
+ let access = AccountRecord::with_entry_mut(&pst.account, |acct|{
+ let acct = acct.ok_or(MgmtError::AccountNotFound)?;
let access = acct.access;
let desc = access.describe_html();
let now = Timestamp::now();
- access.entry(desc)
+ acct.tokens_revealed.entry(desc)
.or_insert(TokenRevelation {
latest: now,
earliest: now,
})
.latest = now;
- access.clone()
- };
+ acct.expire_tokens_revealed();
+ Ok::<_,MgmtError>(access.clone())
+ }).map_err(|(e,_)|e)??;
let token = access
.override_token()
+ .cloned()
.unwrap_or_else(||{
RawToken::new_random()
// xxx disconnect everyone else
self.token_register(token.clone(), iad);
let report = AccessTokenReport {
- url: format!("http://localhost:8000/{}", token.url), // xxx
+ url: format!("http://localhost:8000/{}", token.0), // xxx
};
let report = access
- .server_deliver(&pst, &report);
+ .server_deliver(&pst, &report)?;
report.cloned()
}
#[throws(InternalError)]
fn save_access_now(&mut self) {
self.save_something("a-", |s,w| {
- let pieces = &s.c.g.pieces;
+ let ipieces = &s.c.g.ipieces;
let tokens_players : Vec<(&str, PlayerId)> = {
let global_players = GLOBAL.players.read().unwrap();
s.c.g.tokens_players.tr
};
let aplayers = s.c.g.iplayers.iter().map(
|(player, PlayerRecord { pst, .. })|
- (player, pst)
+ (player, pst.clone())
).collect();
- let isa = InstanceSaveAccesses { pieces, tokens_players, aplayers };
+ let isa = InstanceSaveAccesses { ipieces, tokens_players, aplayers };
rmp_serde::encode::write_named(w, &isa)
})?;
self.c.access_dirty = false;
}
#[throws(StartupError)]
- fn load(name: InstanceName) -> InstanceRef {
+ fn load_game(name: InstanceName) -> InstanceRef {
{
let mut st = GLOBAL.save_area_lock.lock().unwrap();
let st = &mut *st;
let iplayers = {
let a = aplayers;
a.drain()
- }.map(|pst| {
+ }.map(|(player, pst)| {
let u = pu_bc.new();
- PlayerRecord { u, pst }
+ (player, 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 }
+ if !gs.players.contains_key(held) { p.held = None }
}
}
);
},
GameFile { access_leaf, name } => {
- InstanceGuard::load(name)?;
+ InstanceGuard::load_game(name)?;
a_leaves.insert(access_leaf, Used);
},
}
pub use std::time::Duration;
pub use std::sync::{Arc,Mutex,MutexGuard,RwLock,RwLockReadGuard,Condvar};
pub use std::collections::{HashMap,hash_map,HashSet};
+pub use std::hash::Hash;
pub use std::borrow::Borrow;
pub use std::convert::{TryFrom,TryInto};
pub use std::str;
}
let pl = ig.gs.players.byid_mut(player)?;
- let tz = &ig.updates.byid(player)?.tz;
+ let ipl = ig.iplayers.byid(player)?;
+ let tz = &ipl.pst.tz;
let mut pieces : Vec<_> = ig.gs.pieces.iter().collect();
pieces.sort_by_key(|(_,pr)| &pr.zlevel);
id : make_pieceid_visible(gpid),
face : pr.face,
};
- let p = if let Some(p) = ig.pieces.get(gpid) { p }
+ let p = if let Some(p) = ig.ipieces.get(gpid) { p }
else { continue /* was deleted */ };
let defs = p.make_defs(&pri)?;
alldefs.push((pri.id, defs));
player,
defs : alldefs,
uses,
- nick : pl.nick.clone(),
+ nick : ipl.pst.nick.clone(),
load : serde_json::to_string(&DataLoad {
players : load_players,
}).map_err(|e| InternalError::JSONEncode(e))?,
+ // xxx show account accesses
};
trace!("SessionRenderContext {:?}", &src);
(src, client)
use thiserror::Error;
use crate::error::display_as_debug;
use crate::accounts::AccountName;
+use std::hash::Hash;
pub use implementation::PlayerAccessSpec;
pub type Acl<Perm> = Vec<AclEntry<Perm>>;
#[derive(Debug,Clone,Serialize,Deserialize)]
-pub struct AclEntry<Perm> {
+pub struct AclEntry<Perm: Eq + Hash> {
pub account_glob: String,
pub allow: HashSet<Perm>,
pub deny: HashSet<Perm>,
}
#[derive(Debug,Clone,Copy,Serialize,Deserialize)]
+#[derive(Hash,Eq,PartialEq,Ord,PartialOrd)]
enum TablePermission {
AddPlayer,
ChangePieces,
use crate::imports::*;
type Insn = crate::commands::MgmtGameInstruction;
+ type TDE = TokenDeliveryError;
+
pub fn raw_token_debug_as_str(s: &str, f: &mut fmt::Formatter)
-> fmt::Result {
let len = min(5, s.len() / 2);
None
}
fn server_deliver(&self, pst: &PlayerState, token: &AccessTokenReport)
- -> Result<Option<&AccessTokenReport>, AE> {
+ -> Result<Option<&AccessTokenReport>, TDE> {
Ok(None)
}
fn client_deliver(&self, pst: &PlayerState, token: &AccessTokenReport)
- -> Result<(),AE> {
+ -> Result<(), TDE> {
panic!()
}
+ fn describe_html(&self) -> Html {
+ let inner = Html::from_txt(&format!("{:?}", self));
+ Html(format!("<code>{}</code>", inner.0))
+ }
}
#[typetag::serde]
#[typetag::serde]
impl PlayerAccessSpec for FixedToken {
fn override_token(&self) -> Option<&RawToken> {
- Some(self.token)
+ Some(&self.token)
}
fn client_mgi(&self, player: PlayerId) -> Option<MgmtGameInstruction> {
Some(Insn::SetFixedPlayerAccess {
fn client_mgi(&self, player: PlayerId) -> Option<MgmtGameInstruction> {
Some(Insn::ReportPlayerAccesses(player))
}
- #[throws(AE)]
+ #[throws(TDE)]
fn server_deliver(&self, ps: &PlayerState, token: &AccessTokenReport)
-> Option<&AccessTokenReport> {
Some(token)
}
- #[throws(AE)]
+ #[throws(TDE)]
fn client_deliver(&self, ps: &PlayerState, token: &AccessTokenReport) {
println!("access account={} nick={:?} url:\n{}",
&ps.account, &ps.nick, token.url);
self.player, self.client, ig.gs.gen, self.to_send)?;
}
- let pu = &mut ig.updates.get_mut(self.player)
+ let iplayer = &mut ig.iplayers.get_mut(self.player)
.ok_or_else(|| self.trouble("player gonee",()))?;
+ let pu = &mut iplayer.u;
loop {
if let Some(ref mut overflow) = self.overflow {
}
self.overflow = {
let mut overflow = Vec::with_capacity(next_len);
- self.wn.write_next(&mut overflow, &pu.tz, &next)
+ self.wn.write_next(&mut overflow, &iplayer.pst.tz, &next)
.map_err(|e| self.wn.trouble("overflow.write_next",&e))?;
debug!("overflow {} {}, len={}",
&self.wn.player, &self.wn.client, &overflow.len());
continue;
}
- self.wn.write_next(&mut buf, &pu.tz, &next)
+ self.wn.write_next(&mut buf, &iplayer.pst.tz, &next)
.map_err(|e| self.wn.trouble("UpdateReader.write_next",&e))?;
let before = next.when - UPDATE_EXPIRE;
let (update, piece) = match (
gs.pieces.byid_mut(piece),
- self.g.pieces.get(piece),
+ self.g.ipieces.get(piece),
) {
(Ok(pc), Some(p)) => {
gs.max_z.update_max(&pc.zlevel.z);
let update = Arc::new(update);
trace!("PrepareUpdatesBuffer update {:?}", &update);
- for (_tplayer, tplupdates) in &mut self.g.updates {
+ for (_tplayer, PlayerRecord { u: tplupdates, .. })
+ in &mut self.g.iplayers
+ {
tplupdates.push(update.clone());
}
}