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
//! A text-based data format for cryptographic network protocols.
//!
//! ```
//! use nettext::enc::*;
//! use nettext::dec::*;
//! use nettext::crypto::*;
//!
//! let final_payload = {
//!     let keypair = gen_signing_keypair();
//!
//!     // Encode a fist object that represents a payload that will be hashed and signed
//!     let signed_payload = seq([
//!         string("CALL").unwrap(),
//!         string("myfunction").unwrap(),
//!         dict([
//!             ("a", string("hello").unwrap()),
//!             ("b", string("world").unwrap()),
//!             ("c", raw(b"{ a = 12; b = 42 }").unwrap()),
//!             ("d", bytes_split(&((0..128u8).collect::<Vec<_>>()))),
//!         ]).unwrap(),
//!         keypair.public_key().term().unwrap(),
//!     ]).unwrap().encode();
//!     eprintln!("{}", std::str::from_utf8(&signed_payload).unwrap());
//!
//!     let hash = compute_hash(&signed_payload, None);
//!     let sign = compute_signature(&signed_payload[..], &keypair);
//!
//!     // Encode a second object that represents the signed and hashed payload
//!     dict([
//!         ("hash", hash.term().unwrap()),
//!         ("signature", sign.term().unwrap()),
//!         ("payload", raw(&signed_payload).unwrap()),
//!     ]).unwrap().encode()
//! };
//! eprintln!("{}", std::str::from_utf8(&final_payload).unwrap());
//!
//! // Decode and check everything is fine
//! let signed_object = decode(&final_payload).unwrap();
//! let [hash, signature, payload] = signed_object.dict_of(["hash", "signature", "payload"], false).unwrap();
//! let hash = hash.hash().unwrap();
//! let signature = signature.signature().unwrap();
//! let expected_hash = compute_hash(payload.raw(), None);
//! assert_eq!(hash, expected_hash);
//!
//! let object2 = decode(payload.raw()).unwrap();
//!
//! let [verb, arg1, arg2, pubkey] = object2.seq_of().unwrap();
//! let pubkey = pubkey.public_key().unwrap();
//! assert!(verify_signature(&signature, payload.raw(), &pubkey));
//!
//! assert_eq!(verb.string().unwrap(), "CALL");
//! assert_eq!(arg1.string().unwrap(), "myfunction");
//! ```
//!
//! The value of `signed_payload` would be as follows:
//!
//! ```raw
//! CALL myfunction {
//!     a = hello;
//!     b = world;
//!     c = { a = 12; b = 42 };
//!     d = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v
//!       MDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f
//!       YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8;
//!   } pk.ed25519:inYgWFyL_BzZTsXNKp71r2aVct_3Izi_bkerbzOiz94
//! ```
//!
//! And the value of `final_payload` would be as follows:
//! ```raw
//! {
//!   hash = h.b2:B1AnRocS90DmqxynGyvvBNuh-brucNO7-5hrsGplJr0;
//!   payload = CALL myfunction {
//!     a = hello;
//!     b = world;
//!     c = { a = 12; b = 42 };
//!     d = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v
//!       MDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f
//!       YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8;
//!   } pk.ed25519:inYgWFyL_BzZTsXNKp71r2aVct_3Izi_bkerbzOiz94;
//!   signature = sig.ed25519:LvLC1gHxNxUH44HHQRO-zWtLM4WyXhiYLFr94qTdI311Wa-kmgZsaWqSWe3jcjkS4PnsWSNt5apgbhR68cWWCg;
//! }
//! ```
//!
//! Note that the value of `text1` is embedded as-is inside `text2`. This is what allows us
//! to check the hash and the signature: the raw representation of the term hasn't changed.

pub mod dec;
pub mod enc;

#[cfg(feature = "dryoc")]
pub mod crypto;

#[cfg(feature = "serde")]
pub mod serde;

// ---- syntactic elements of the data format ----

pub(crate) const DICT_OPEN: u8 = b'{';
pub(crate) const DICT_CLOSE: u8 = b'}';
pub(crate) const DICT_ASSIGN: u8 = b'=';
pub(crate) const DICT_DELIM: u8 = b';';
pub(crate) const LIST_OPEN: u8 = b'[';
pub(crate) const LIST_CLOSE: u8 = b']';
pub(crate) const LIST_DELIM: u8 = b';';
const BASE_EXTRA_CHARS: &[u8] = b".,:?!@$^<>|&#'_-+*/%";
const STR_EXTRA_CHARS: &[u8] = b"\\";

#[inline]
pub(crate) fn is_string_char(c: u8) -> bool {
    c.is_ascii_alphanumeric() || BASE_EXTRA_CHARS.contains(&c) || STR_EXTRA_CHARS.contains(&c)
}

#[inline]
pub(crate) fn is_whitespace(c: u8) -> bool {
    c.is_ascii_whitespace()
}

pub(crate) fn debug(x: &[u8]) -> &str {
    std::str::from_utf8(x).unwrap_or("<invalid ascii>")
}