Skip to main content

otter/
commands.rs

1// Copyright 2020-2021 Ian Jackson and contributors to Otter
2// SPDX-License-Identifier: AGPL-3.0-or-later
3// There is NO WARRANTY.
4
5use crate::prelude::*;
6
7#[derive(Debug,Serialize,Deserialize)]
8pub enum MgmtCommand {
9  Noop,
10  SetSuperuser(bool),
11  SetRestrictedSshScope { key: sshkeys::KeySpec },
12
13  CreateAccount(AccountDetails),
14  UpdateAccount(AccountDetails),
15  DeleteAccount(AccountName),
16  ListAccounts { all: Option<bool> },
17
18  SelectAccount(AccountName), // success does not mean account exists
19  CheckAccount, // success *does* mean account exists and we have access
20
21  CreateGame {
22    game: InstanceName,
23    insns: Vec<MgmtGameInstruction>,
24  },
25  ListGames { all: Option<bool> },
26  AlterGame {
27    game: InstanceName,
28    insns: Vec<MgmtGameInstruction>,
29    how: MgmtGameUpdateMode,
30  },
31  DestroyGame {
32    game: InstanceName,
33  },
34
35  ClearBundles {
36    game: InstanceName,
37  },
38  UploadBundle {
39    game: InstanceName,
40    size: usize,
41    hash: bundles::Hash,
42    kind: bundles::Kind,
43    #[serde(default)] progress: ProgressUpdateMode,
44  },
45  ListBundles {
46    game: InstanceName,
47  },
48  DownloadBundle {
49    game: InstanceName,
50    id: bundles::Id,
51  },
52
53  LibraryListLibraries {
54    game: InstanceName,
55  },
56  LibraryListByGlob {
57    game: InstanceName,
58    lib: Option<String>,
59    pat: String,
60  },
61
62  SshListKeys,
63  SshAddKey { akl: sshkeys::AuthkeysLine },
64  SshDeleteKey { index: usize, id: sshkeys::Id },
65  ThisConnAuthBy, // -> Fine or SshKeySpec
66  SshReinstallKeys, // managment only
67
68  LoadFakeRng(Vec<String>),
69  SetFakeTime(FakeTimeSpec),
70}
71
72//---------- Accounts file ----------
73
74#[derive(Debug,Clone,Hash,Eq,PartialEq)]
75#[derive(Serialize,Deserialize)]
76pub struct SshFingerprint(pub String);
77
78#[derive(Debug,Serialize,Deserialize)]
79pub struct AccountDetails {
80  pub account: AccountName,
81  pub nick: Option<String>,
82  pub timezone: Option<String>,
83  pub layout: Option<PresentationLayout>,
84  #[serde(flatten)]
85  pub access: Option<Box<dyn PlayerAccessSpec>>,
86//  pub invite: Acl<AccountPerm>,   todo
87}
88
89//#[derive(Debug,Clone,Copy,Serialize,Deserialize)]
90//enum AccountPerm {
91//  InviteToGame,
92//  GameRunnerResetToken,
93//}
94
95
96#[derive(Debug,Serialize,Deserialize)]
97pub enum MgmtResponse {
98  Fine,
99  Progress(ProgressInfo<'static>),
100  Error { error: MgmtError },
101  AlterGame { error: Option<MgmtError>, responses: Vec<MgmtGameResponse> },
102  AccountsList(Vec<Arc<AccountName>>),
103  GamesList(Vec<Arc<InstanceName>>),
104  Libraries(Vec<LibraryEnquiryData>),
105  LibraryItems(Vec<ItemEnquiryData>),
106  Bundles { bundles: MgmtBundleList },
107  Bundle { bundle: bundles::Id },
108  SshKeys(Vec<sshkeys::MgmtKeyReport>),
109  SshKeyAdded { index: usize, id: sshkeys::Id },
110  ThisConnAuthBy(MgmtThisConnAuthBy),
111}
112
113pub type MgmtBundleList = BTreeMap<bundles::Id, bundles::State>;
114
115#[derive(Debug,Clone,Serialize,Deserialize)]
116pub enum MgmtThisConnAuthBy {
117  Local,
118  Ssh { key: sshkeys::KeySpec },
119}
120
121#[derive(Debug,Serialize,Deserialize)]
122pub enum MgmtGameInstruction {
123  Noop,
124  Info,
125  SetTableSize(Pos),
126  SetTableColour(ColourSpec),
127
128  InsnMark(Box<[u8]>),
129  /// For testing, mostly.
130  Synch, SynchLog,
131  /// For testing only
132  PieceIdLookupFwd { piece: PieceId, player: PlayerId, },
133  /// For testing only
134  PieceIdLookupRev { vpid: VisiblePieceId, player: PlayerId, },
135
136  ListPieces,
137  AddPieces(PiecesSpec),
138  DeletePiece(PieceId),
139  DeletePieceAlias(String),
140  DefinePieceAlias { alias: String, target: Box<dyn PieceSpec> },
141
142  ClearGame { },
143  ResetFromNamedSpec { spec: String },
144  ResetFromGameSpec { spec_toml: String },
145
146  ResetPlayerAccess(PlayerId),
147  RedeliverPlayerAccess(PlayerId),
148
149  JoinGame { details: MgmtPlayerDetails },
150  UpdatePlayer { player: PlayerId, details: MgmtPlayerDetails },
151  LeaveGame(PlayerId),
152
153  SetLinks(HashMap<LinkKind, UrlSpec>),
154  RemoveLink { kind: LinkKind },
155  SetLink { kind: LinkKind, url: UrlSpec },
156
157  ClearLog,
158  SetACL { acl: Acl<TablePermission> },
159  // RemovePlayer { player: PlayerId },  todo, does a special setacl
160}
161
162#[derive(Debug,Serialize,Deserialize)]
163pub struct MgmtPlayerDetails {
164  pub nick: Option<String>,
165}
166
167#[derive(Debug,Serialize,Deserialize)]
168pub enum MgmtGameResponse {
169  Fine,
170  Info(MgmtGameResponseGameInfo),
171  InsnMark(Box<[u8]>),
172  InsnExpanded,
173  Synch(Generation),
174
175  InternalPieceId(Option<PieceId>),
176  VisiblePieceId(Option<VisiblePieceId>),
177
178  Pieces {
179    pieces: Vec<MgmtGamePieceInfo>,
180    pcaliases: BTreeSet<String>,
181  },
182
183  JoinGame {
184    nick: String,
185    player: PlayerId,
186    token: AccessTokenReport,
187  },
188  PlayerAccessToken(AccessTokenReport),
189}
190
191#[derive(Debug,Clone,Serialize,Deserialize)]
192pub struct AccessTokenInfo { pub url: String }
193
194#[derive(Debug,Clone,Serialize,Deserialize)]
195pub struct AccessTokenReport { pub lines: Vec<String> }
196
197#[derive(Debug,Clone,Serialize,Deserialize)]
198pub struct MgmtGameResponseGameInfo {
199  pub table_size: Pos,
200  pub players: SecondarySlotMap<PlayerId, MgmtPlayerInfo>,
201  pub links: Vec<(LinkKind, UrlSpec)>,
202}
203
204#[derive(Debug,Clone,Serialize,Deserialize)]
205pub struct MgmtPlayerInfo {
206  pub account: AccountName,
207  pub nick: String,
208}
209
210#[derive(Debug,Clone,Serialize,Deserialize)]
211pub struct MgmtGamePieceInfo {
212  pub piece: PieceId,
213  pub itemname: shapelib::GoodItemName,
214  #[serde(flatten)]
215  pub visible: Option<MgmtGamePieceVisibleInfo>,
216}
217
218#[derive(Debug,Clone,Serialize,Deserialize)]
219pub struct MgmtGamePieceVisibleInfo {
220  pub pos: Pos,
221  pub face: FaceId,
222  pub desc_html: Html,
223  pub bbox: Rect,
224}
225
226#[derive(Debug,Copy,Clone,Serialize,Deserialize)]
227pub enum MgmtGameUpdateMode {
228  Online,
229  Bulk,
230}
231
232#[derive(Debug,Copy,Clone,Eq,PartialEq,Ord,PartialOrd,Serialize,Deserialize)]
233pub enum ProgressUpdateMode {
234  None,
235  Simplex,
236  Duplex,
237}
238impl Default for ProgressUpdateMode {
239  fn default() -> Self { PUM::None }
240}
241
242#[derive(Debug,Clone,Error,Serialize,Deserialize)]
243pub enum MgmtError {
244  #[error("failed to parse protocol command: {0}")] CommandParseFailed(String),
245  #[error("not authorised")]                         AuthorisationError,
246  #[error("superuse authorisation requiredr")] SuperuserAuthorisationRequired,
247  #[error("command requires account specified")]     SpecifyAccount,
248  #[error("command requires nick specified")]        MustSpecifyNick,
249  #[error("object or item already exists")]          AlreadyExists,
250  #[error("game already has player with that nick")] NickCollision,
251  #[error("{0}")] GameBeingDestroyed (#[from] GameBeingDestroyed),
252  #[error("game not found")]                         GameNotFound,
253  #[error("{0}")] AccountNotFound     (#[from] AccountNotFound),
254  #[error("{0}")] PlayerNotFound      (#[from] PlayerNotFound),
255  #[error("piece not found in game")]                PieceNotFound,
256  #[error("limit exceeded")]                         LimitExceeded,
257  #[error("server failure: {0}")]                    ServerFailure(String),
258  #[error("{0}")] ZCoordinateOverflow (#[from] zcoord::Overflow),
259  #[error("bad glob pattern: {pat:?}: {msg}")]
260                                           BadGlob { pat: String, msg: String },
261  #[error("{0}")] BadSpec             (#[from] SpecError),
262  #[error("access token delivery failed: {0}")]
263                  TokenDeliveryFailed (#[from] TokenDeliveryError),
264  #[error("{0}")] CoordinateOverflow  (#[from] CoordinateOverflow),
265  #[error("TOML syntax error: {0}")]                 TomlSyntaxError(String),
266  #[error("TOML structure error: {0}")]              TomlStructureError(String),
267  #[error("{0}, command not supported")] RngIsReal(#[from] RngIsReal),
268  #[error("{0}, command not supported")] TimeIsReal(#[from] TimeIsReal),
269  #[error("upload truncated")]                       UploadTruncated,
270  #[error("upload corrupted")]                       UploadCorrupted,
271  #[error("too many bundles")]                       TooManyBundles,
272  #[error("bad bundle: {0}")]                        BadBundle(String),
273  #[error("bundle not found")]                       BundleNotFound,
274  #[error("bundle(s) in use, cannot clear ({0})")]   BundlesInUse(String),
275  #[error("game spec not found")]                    GameSpecNotFound,
276  #[error("game contains invalid UTF-8")]            GameSpecInvalidData,
277  #[error("idle timeout waiting for mgmt command")]  IdleTimeout,
278  #[error("upload took too long (timed out)")]       UploadTimeout,
279  #[error("ssh keys are only for main subaccount")]  NoSshKeysForSubaccount,
280  #[error("ssh key not found")]                      SshKeyNotFound,
281  #[error("ssh key id default, ie invalid")]         InvalidSshKeyId,
282  #[error("ssh key invalid: {0}")] InvalidSshKey(#[from] sshkeys::KeyError),
283  #[error("command forbides account specified")]     AccountSpecified,
284}
285
286impl AccountDetails {
287  pub fn default(account: AccountName) -> AccountDetails { AccountDetails {
288    account,
289    nick: None, timezone: None, layout: None, access: None,
290  } }
291}
292
293impl From<InternalError> for MgmtError {
294  fn from(e: InternalError) -> MgmtError {
295    MgmtError::ServerFailure(format!("internal error: {}", &e))
296  }
297}
298
299impl AccessTokenInfo {
300  pub fn report(self) -> Vec<String> {
301    vec![
302      "Game access url (personal and private):".to_string(),
303      self.url,
304    ]
305  }
306}