; This grammar is written using ABNF, as defined in RFC 5234
; (https://datatracker.ietf.org/doc/html/rfc5234), from which ALPHA, DIGIT
; and HEXDIG are borrowed
;
; Furthermore, this grammar is based on and specialises the generic URI
; specification (https://datatracker.ietf.org/doc/html/rfc3986) for Radicle,
; from which 'query', 'fragment', 'host', 'port', 'unreserved', 'pchar', ...
; are borrowed
; tag::overview[]
; RFC 3986 defines the syntax for 'query' and 'fragment'
rad = rad-scheme ":" rad-auth-and-resource rad-resource-type-and-identity [ "?" query ] [ "#" fragment ]
/ rad-scheme ":" rad-legacy
rad-scheme = "rad"
; RFC 3986 allows an absolute resource path without authority, i.e. a URI
; that looks like this: rad:/{RID}/{NID}
; We don't include that sort of URI in this scheme, because it's, strictly
; speaking, unnecessary in this context, and may seem confusing.
rad-auth-and-resource = "//" [ rad-auth ] "/" rad-resource ; abs. resource "path" with authority
/ rad-resource ; rel. resource "path"
; rad-legacy are URLs defined by RIP #3. That URL doesn't quite hold up
; against the general URI syntax defined in RFC3986, in so far that a node
; (identified with NID or NID@host) is the conceptual authority, not
; a repository.
; To be noted is that RIDs and NIDs are distinct enough that there should not
; be any ambiguity between rad-auth-and-resource and rad-legacy.
; Of all current implementations, this mostly impacts the git remote helper
; git-remote-rad.
rad-legacy = "//" rad-resource
; end::overview[]
; The NID is conceptually ambiguous from an RFC 3986 syntax perspective:
;
; authority = [ userinfo "@" ] host [ ":" port ]
;
; Is the NID 'userinfo' or 'host'? In a sense, it currently is a little bit
; of both. Furthermore, Radicle requires that if a hostname (+ port) is
; given, the NID must be given too. Therefore, instead of using 'authority'
; strictly as given by RFC 3986, we allow ourselves a slightly different
; syntax, which is fine. Do note also, that a 'host' can still be made to
; look like an NID, so programs dealing directly with the Radicle network
; will have to be prepared to handle that ambiguity appropriately.
; tag::authority[]
;
; TODO: There already are ways to resolve its NID from a hostname.
; For example, if `radicle-httpd` runs next to the node, then it can be
; queried for its NID via HTTPS. Also, there are ways to use DNS TXT records,
; which is also done for DNS-SD.
; We would therefore likely make the `rad-node` optional, or even omit it.
; If `rad-node` is sufficiently different from a DNS name (it probably is
; not), then we can even omit "@".
rad-auth = rad-node [ "@" rad-host-and-port ]
; TODO: Port might be optional in the future, once we have convinced IANA
; to assign one to us. There currently is no default port.
rad-host-and-port = host ":" port
; rad-node is a semantic symbol, to signify intent
rad-node = rad-nid
; An NID is an ID that is ultimately a public key in the Radicle network.
; This is used to uniquely represent a node, or a user (also a node), or a
; namespace within a repository.
; It is a [multibase] and [multicodec] encoded string, using [base58btc] as the multibase
; and the codec for the underlying Ed25519 public key.
;
; It is always 48 characters long, including the initial "z6Mk",
; the `z` corresponding to the base, and `6Mk` corresponding to the codec.
;
; [base58btc]: https://digitalbazaar.github.io/base58-spec/
; [multibase]: https://www.ietf.org/archive/id/draft-multiformats-multibase-07.html
; [multicodec]: https://github.com/multiformats/multicodec/
;
; TODO: New NIDs in the future? Even this might change, as we allow rotating
; keys for nodes. But this is even further out than stable IDs for users.
rad-nid = "z6Mk" 44( base58btc )
; end::authority[]
; tag::resource[]
rad-resource = rad-repository [ "/" rad-namespace ]
; rad-repository is a semantic symbol, to signify intent
rad-repository = rad-rid
; rad-namespace is a semantic symbol, to signify intent
rad-namespace = rad-nid
; An RID uniquely represents a git repository in the Radicle network.
; It is a [multibase] encoded string of a Git OID, using [base58btc] as the multibase.
;
; It is 28 or 29 characters long, including the initial multibase identifier "z".
;
; [base58btc]: https://digitalbazaar.github.io/base58-spec/
; [multibase]: https://www.ietf.org/archive/id/draft-multiformats-multibase-07.html
;
; TODO: In the near future (as soon as Git repositories with SHA-256 object
; format become popular, which will be in late 2026), we will have to extend
; the syntax of RIDs. Leave a note here so that implementors know this and
; make design decisions accordingly.
rad-rid = "z" 27*28( base58btc )
; end::resource[]
; tag::resource-type-and-identity[]
; 'rad-resource-type-and-identity' is empty by default, and more resource
; types and identities will be added further on, using the ABNF =/ operator,
; see RFC 5234 "3.3. Incremental Alternatives: Rule1 =/ Rule2"
rad-resource-type-and-identity = ""
; tag::git-type-and-identity[]
; For git specific resource types
rad-resource-type-and-identity =/ git-commit / git-tree / git-blob / git-tag
git-commit = "/" "commit" "/" git-obj-or-ref
git-tree = "/" "tree" "/" git-obj
git-blob = "/" "blob" "/" git-obj
git-tag = "/" "tag" "/" git-obj-or-ref
; Note: this rule will always be ambiguous, as something that looks like a
; git object ID could very well be a branch or a tag.
; Some git commands will warn when such refs are used, but not all.
git-obj-or-ref = git-obj / git-ref
; Currently, only sha1 identities are supported.
git-obj = git-sha1
; HEXDIG is defined in RFC 5234
git-sha1 = 40HEXDIG
; unreserved is defined in RFC 3986. Not that this syntax allows much
; more than what can be specified for a git reference. It's left to
; the implementation to parse and apply semantics properly.
; Our best reference for how to interpret and parse git references is
; https://git-scm.com/docs/protocol-common.html
git-ref = 1*unreserved *( "/" 1*unreserved )
; end::git-type-and-identity[]
; tag::rad-cob-type-and-identity[]
; For Radicle collaborative object resource types
rad-resource-type-and-identity =/ rad-cob / rad-cobs
rad-cob = "/" "cob" "/" rad-cob-type "/" rad-cob-obj
rad-cobs = "/" "cob" "/" rad-cob-type
; The COB type is like a reversed FQDN. The syntax spec is derived from
; https://datatracker.ietf.org/doc/html/rfc1034#section-3.5
rad-cob-type = rad-cob-label 1*( "." rad-cob-label )
rad-cob-label = 1*( ALPHA / DIGIT ) *( "-" 1*( ALPHA / DIGIT ) )
; syntactically, radicle COB objects and object revisions are git objects
; semantically, they are as well, at least for now
rad-cob-obj = git-obj
; end::rad-cob-type-and-identity[]
; end::resource-type-and-identity[]
; tag::rfc5234[]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Copied from RFC 5234
ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
DIGIT = %x30-39 ; 0-9
HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
; Copied from RFC 3986
host = IP-literal / IPv4address / reg-name
port = *DIGIT
IP-literal = "[" ( IPv6address / IPvFuture ) "]"
IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
IPv6address = 6( h16 ":" ) ls32
/ "::" 5( h16 ":" ) ls32
/ [ h16 ] "::" 4( h16 ":" ) ls32
/ [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
/ [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
/ [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
/ [ *4( h16 ":" ) h16 ] "::" ls32
/ [ *5( h16 ":" ) h16 ] "::" h16
/ [ *6( h16 ":" ) h16 ] "::"
h16 = 1*4HEXDIG
ls32 = ( h16 ":" h16 ) / IPv4address
IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
dec-octet = DIGIT ; 0-9
/ %x31-39 DIGIT ; 10-99
/ "1" 2DIGIT ; 100-199
/ "2" %x30-34 DIGIT ; 200-249
/ "25" %x30-35 ; 250-255
reg-name = *( unreserved / pct-encoded / sub-delims )
pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
query = *( pchar / "/" / "?" )
fragment = *( pchar / "/" / "?" )
pct-encoded = "%" HEXDIG HEXDIG
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
/ "*" / "+" / "," / ";" / "="
; end::rfc5234[]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Base58 Bitcoin Alphabet
base58btc = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"