1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
#![forbid(bad_style, warnings)] #![deny(deprecated, drop_with_repr_extern, improper_ctypes, missing_docs, non_shorthand_field_patterns, overflowing_literals, plugin_as_library, private_no_mangle_fns, private_no_mangle_statics, raw_pointer_derive, stable_features, unconditional_recursion, unknown_lints, unsafe_code, unsigned_negation, unused_allocation, unused_attributes, unused_comparisons, unused_features, unused_parens, while_true)] #![warn(trivial_casts, trivial_numeric_casts, unused, unused_extern_crates, unused_import_braces, unused_qualifications, unused_results, variant_size_differences)] #![allow(dead_code)] //! # Example of Proposed Structured Data for the SAFE Network //! //! This is an extension of the proposed implementation described in [RFC 0000][0]. It conflicts //! with the "Detailed Design" section, but is in alignment with the other main sections of the RFC. //! //! It may be helpful to view the raw source either [on GitHub][1] or [in the docs][2] while reading //! this, since it shows all the elements together in one place. //! //! The main differences between the original proposal and this are: //! //! * Clearly-identified immutable and mutable parts //! * Multiple versions retained in a single place //! * No signatures retained in the `Data` element //! //! This proposal is slightly more complex than the original, but not largely so. This added //! complexity provides greater ease of use and extensibility of rules. //! //! //! //! # Authorisation of Mutating Requests //! //! This is more flexible than the process described in the original proposal. The idea is that //! each owners' public key is given a weighting. To authorise a mutation, enough signatures must //! be provided so that the combined weight of the corresponding public keys exceeds a limit. The //! limit can itself be changed via an authorised request. //! //! This basic system works well for a single owner (where the calculation is almost a no-op), but //! also is fairly simple to understand in the case of multiple owners. The rules for the network //! to implement this are as simple as in the original proposal, and the cost in terms of data size //! is minimal; a `u64` plus another `u64` per owner. For a single owner, both of these elements //! could be removed from the serialised `Data`, giving no additional cost. //! //! I have also not included the signatures as part of the `Data` since these only need to be //! examined by the network at the point when the `Data` is mutated, i.e. when the request is //! received. This may be an oversight though, in which case they can be added where required. //! //! //! //! # Immutable Part of `Data` //! //! The `FixedAttributes` are immutable for the lifetime of the `Data`. As well as the original //! `type_tag` and `id` whose meanings are unchanged, I have added the following fields: //! //! * `max_versions`: self-explanatory, but the rules for handling exceeding this limit would need //! to be decided. An easy option would be to simply pop the oldest version. //! * `min_retained_count`: we can archive old versions if required. The process would need to be //! defined, but these archived parts could become immutable and hence even stored as //! `ImmutableData`. At the point when the old versions are stripped out of the `Data` for //! archiving, this field would specify how many versions to retain. This could be just one if the //! data type in question normally only needs the single most recent version (e.g. user's session //! packet) or could be many if the data often uses several versions (e.g. file browser with //! rollback capabilities). //! * `data`: This can be used for any purpose appropriate to that particular `Data` type. //! //! //! //! # Mutable Part of `Data` //! //! The `MutableAttributes` can be changed if enough owners sign a request to change them. Such a //! request would need to also come with an incremented `Version` to avoid synchronisation issues. //! //! The `owner_keys` and `min_weight_for_consensus` relate to the authorisation process described //! above. The remaining fields are: //! //! * `expiry_date`: a (probably controversial!) idea to allow the `Data` to be removed from the //! network on a given date. This would not require exactness or an NTP server - it would be an //! approximate time point at which the the managing nodes would remove the `Data` from their //! records. This isn't just to save space on the network, more that it could be a useful feature //! for users. //! * `data`: as per `FixedAttributes::data`. //! //! //! //! # Versions //! //! Another major departure from the original proposal is to hold a `Vec<Version>` rather than a //! single one. This would comprise the most recent versions, but may be only one if //! `FixedAttributes::max_versions == 1` or may be all versions if total `Data` size permits. //! //! In at least two of our own use-cases (session packet and directory listings), we need to be able //! to store and retrieve more than just the most recent version. This can be done in the original //! proposal by serialising this information into the single `data` field, but this proposal makes //! that task more obvious and less error-prone. //! //! Furthermore, by exposing the versions in this way, it leaves scope for the network to be able to //! handle archiving old versions without any client interaction. This wouldn't be possible if the //! network weren't able to access the list of versions, as is the case in the original proposal. //! //! The `Version` struct comprises two elements: //! //! * `index`: This will be an incrementing value. To maintain simplicity we can enforce a strict //! increment-by-one policy, i.e. if a new version arrives out of sequence it will be rejected. //! However, this isn't an issue as long as the client has to send the entire `Data` every time a //! mutation is made. We can look at re-implementing the branching protocol as per the //! `StructuredDataVersions` from the C++ codebase, but I don't think that was a popular class. //! * `data`: per-version arbitrary data. //! //! //! //! # General //! //! There would still be a hard upper limit on the total size of a `Data` instance as described in //! the original proposal. //! //! While some of the rules can be ignored in the initial implementation of this (e.g. handling //! archiving) I don't envisage this being more difficult to implement than the original proposal. //! This also leaves room for eventual improvement in efficiency, for example by allowing clients to //! send only a new `Version` rather than always sending a full `Data` packet. //! //! //! //! [0]: https://github.com/maidsafe/rfcs/pull/11/files "SAFE Network RFC 0000" //! [1]: https://github.com/Fraser999/StructuredData/blob/master/src/structured_data.rs //! "Raw source for Structured Data on GitHub" //! [2]: http://fraser999.github.io/StructuredData/src/structured_data/structured_data.rs.html //! "Raw source for Structured Data in the docs" extern crate routing; extern crate sodiumoxide; extern crate time; /// Structured Data implementation. pub mod structured_data; use routing::NameType; use sodiumoxide::crypto::sign; use structured_data::{Data, FixedAttributes, KeyAndWeight, MutableAttributes, Version}; type StructuredData = Data; fn main() { let mut user_session_packet = StructuredData { fixed_attributes: FixedAttributes { type_tag: 1, id: NameType::new([0u8; 64]), max_versions: 10, min_retained_count: 5, data: vec!['A' as u8, 'B' as u8, 'C' as u8] }, mutable_attributes: MutableAttributes { owner_keys: vec![ KeyAndWeight { key: sign::gen_keypair().0, weight: 1 } ], min_weight_for_consensus: 0, expiry_date: time::now() + time::Duration::days(36524), data: vec!['D' as u8, 'E' as u8, 'F' as u8] }, versions: vec![ Version { index: 0, data: vec!['v' as u8, '0' as u8] } ] }; println!("user_session_packet:\n{:?}", user_session_packet); user_session_packet.versions.push(Version{ index: 1, data: vec!['v' as u8, '1' as u8] }); println!("user_session_packet:\n{:?}", user_session_packet); }