Since the service performs further I/O (e.g. uses SQLite), it can keep
the reactor runtime thread busy for long periods. Emit a warning if that
is the case.
100 ms is quite relaxed, this is to only catch severe cases and avoid
spamming the log.
Since this is a binary crate, `pub` is not necessary. By removing `pub`
at the boundary of the crate (`src/main.rs`) and working our way in we
obtain tighter boundaries. This enables dead-code elimination and more
liberal lints (see following two commits).
A new module to model the domain of protocol lines and commands
being exchanged is introduced.
This is to increase readability and to pave the way for a
future sans I/O version of the binary crate.
Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
In case a `cd` command is to be processed, no replacement of
environment variables in arguments is performed. This means that the
definition of `let mut args` and the replacement itself can be moved
closer to where `args` is then actually used, which is easier to
reason about.
Configuration calls to `escargot` were removed in commit `4894657b`.
An unintentional consequence are spurious failures to invoke freshly
compiled binaries in CLI tests.
Bring back the explicit configuration to remedy.
In the previous refactoring in commit `4894657b`, the order of entries
in `$PATH` was changed unintentionally. Revisit the order and use
nicer APIs to handle paths.
The node being connected to may be blocking the connecting node.
In this case the service will emit an event that says the connecting
node is disconnected.
Check the events for this event as well as `Event::PeerConnected`.
The service learns to consult the policies database to check if a
`NodeId` is blocked.
When a node is blocked the service will prevent any inbound or
outbound connection.
In the case of an inbound connection, a disconnect must be submitted
to the reactor.
In the case of an outbound connection, nodes are filtered out from any
connection lists, and prevented from submitting a connect to the
reactor.
Introduces the ability to explicitly block a peer via the node control
socket. Previously, the node only exposed follow and unfollow commands.
While the underlying policy database schema supported a Block variant,
there was no mechanism to trigger this state via the client handle.
The new block command:
1. Updates the node's follow policy to Block.
2. Immediately disconnects the peer if a session is active.
The previous code would convert the `remote` into a `Component` on
each iteration of the loop.
The conversion now happens outside of the loop and can be reused.
To prepare for future changes, warn users if their config file currently
does not explicitly specify a default seeding policy.
Because there are now potentially multiple warnings generated that all
relate to the configuration file, group these together and adjust the
wording to be more uniform.
The `SeedingPolicy` type now requires a `Scope`, however existing
configurations allow it to not be specified.
Introduce a `radicle::node::config::Scope` type that allows for an
optional `policy::Scope`. The default value resolves to
`policy::Scope::All`. This preserves backwards compatibility for
existing configurations.
As part of the work to change the default scope, one of the tests
surfaced an issue where the topology was Alice <--> Bob <--> Eve,
however because of the previous 'all' scope Alice could receive Eve
references via Bob, but since the scope was changed to 'follow' Alice
would need to have a direct connection to Eve. TODO left here for future
consideration.
The previous implementation used 'all' as the default for scope, this
could lead to surprising behaviour where a user would fetch all
references for cloned and seeded repositories. Instead have a progressive,
safe by default value - where it fetches only 'followed' references. Later
a user can decide to set the scope to 'all'. NOTE: the default policy
scope was not changed from 'all' and is intended to be changed at a
later date.
Previous implementation used the default scope when a scope wasn't
present. Scope is not present when the policy is block, because block
doesn't have a scope. Instead use an empty string when printing the
block policy.
The previous resolution would favour one `Oid` over another, due to
the call to `Option::or`.
This was found to result in scenarios where an identity document being
advertised was older than the identity document that the fetching node
already has, and would not fetch namespaces by newly added delegates.
The resolution logic is updated to handle all cases for these `Oid`s,
and when they are both present, uses a commit graph check to see which
one is the latest.
An e2e test is added to check ensure behaviour works correctly.
Fixes [CVE-2026-0810].
The `gix` crates require updating due to the security vulnerability above.
They require updating together in lock-step so both `radicle-fetch`
and `radicle-oid` are affected.
`radicle-oid` handles the non-exhaustive nature of `ObjectId`.
`radicle-fetch` updates to the new API types, however, this comes with
some updates to the `ls_refs` protocol.
A regression is documented in [gitoxide issue 2429].
The regression highlighted that it is the duty of the caller to also
filter the outcome of ls-refs, and `ref-prefix` is simply an
optimisation.
The ls-refs code is refactored to better represent and handle this operation.
[CVE-2026-0810]: https://www.cve.org/CVERecord?id=CVE-2026-0810
[gitoxide issue 2429]: https://github.com/GitoxideLabs/gitoxide/issues/2429
Better represent the domain by introducing the `RefPrefix` type.
It captures the different `ref-prefix` values that can be used in the
`ls-refs` step of the protocol.
These are turned into their equivalent `ref-prefix` values using
`RefPrefix::into_bstring`.
This better connects the `ProtocolStage::ls_refs` method with the
`ls_refs::run` through the use of the `RefPrefix` type, rather than
any set of `BString` values.
The Git protocol specification states about `ls-refs`[^1]:
> ref-prefix <prefix>
> When specified, only references having a prefix matching one of
> the provided prefixes are displayed. Multiple instances may be
> given, in which case references matching any prefix will be
> shown. Note that this is purely for optimization; a server MAY
> show refs not matching the prefix if it chooses, and clients
> should filter the result themselves.
The `ref-prefix` arguments should not be relied on and post-filtering
after the ls-refs invocation should also be performed.
[^1]: https://git-scm.com/docs/protocol-v2#_ls_refs
The `Responder` is a oneshot channel where it sends one result and the
other side receives that result.
This surfaced that the `AnnounceRefs` command was returning one result
at a time. At the moment, this is only used for a single namespace,
the local user's.
Refactors every command that uses a `Sender` to use a `Responder`
instead. There is an exception for `QueryState` since its usage
differs slightly to the other cases.
To make the usage more ergonomic, constructors for the variants that
require a `Responder` are added.
Introduce a `Responder` type that wraps a
`crossbeam_channel::Sender<Result<T>>`.
It provides a standard constructor and three sending methods: one for
a `Result`, one for an `Ok` value, and one for an `Err` value.
Slightly relax the conditions under which an error is considered to
indicate that the SSH agent is not running, to accommodate differences
in the API of named pipes on Windows vs. Unix domain sockets.
The construction of the debug object is unwieldy, and error prone
(for example, renamed struct members have to be manually renamed
in the serialization code, see "refs" vs. "refsAt").
Use derived serializers where possible to make this easier to maintain.
`radicle-protocol` depends on `cyphernet`, but only uses the module
`cyphernet::addr`, which is a re-export of `cypheraddr`, which is
unnecessary (as `cypheraddr` alone would do), and invites accidentally
pulling in more dependencies via `cyphernet`.
To prevent accidentally depending on further types from `cyphernet`,
change this to more conservativeley depend on `cypheraddr`.
Help text generated by `clap` contains the full file name (including
extension) of the binary. On Unix-like systems, binaries commonly do not
have any file extension. On Windows, ".exe" is common.
To allow testing such output, introduce a new marker "[EXE]" that is
substituted accordingly. The idea and syntax is taken from
https://docs.rs/trycmd/1.0.0/trycmd/#toml
Report: <https://www.cve.org/CVERecord?id=CVE-2025-58160>
This vulnerability was introduced via the `test-log` crate.
Updating to `0.2.19` in turn updates `tracing-subscriber` to `0.3.22`
which is in the acceptable upgrade range.
Using empty `Vec`s to mean "all refs" can be confusing. Avoid the chance
of confusion by introduction of a new type that clearly describes which
refs should be fetched.
It was found that if a fetch was started for a specific `Vec<RefsAt>`
and a following general, i.e. empty, `Vec<RefsAt>` was queued, the
subscriber for the general one would be removed when the specific
fetch completed.
This behaviour is undesirable, since the general fetch could
potentially fetch more data.
To prevent this from happening, the `Vec<RefsAt>` is added to the
`FetchKey`, so that `subscribers` are matched with the set of
references they want to hear results for.
A test was added for the above scenario.
When decoding an input string for a `RepoId`, the base was never checked.
This change ensures that the base is checked and will error otherwise.
To allow for future base codes, the change introduces a sanctioned set
of base codes, which only contains `Base58Btc` for now.
Tests are added to ensure that parsing is correct and includes a valid
multibase code 'Z', which is not the expected 'z'.
Recent versions of Windows support Unix Domain Sockets natively, see
<https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/>.
By using that feature instead of Windows named pipes, the difference
for handling communication via the control socket comparing Unix-like
systems and Windows becomes smaller:
1. No special paths like `\.\pipe\…` have to be handled.
2. Not two I/O mechanisms are abstracted (named pipe and UDS) but
just one.
3. `winpipe` relies on background threads while `uds_windows` does
not.
Once the feature `windows_unix_domain_sockets` (which is tracked at
https://github.com/rust-lang/rust/issues/150487) stabilizes, it will
likely be possible to just drop the dependency `uds_windows` and use
the implementation in `std::os::windows::net` directly.
The paths checked by `fn exists` are well-known Unix locations, and not
meaningful to check on Windows.
Provide an implementation of `fn exists` for Windows, which uses
`which.exe` to look for binaries, and use that to provide sane defaults
for Windows.
Git for Windows horribly mangles the configured binaries through a POSIX
`sh`-like environment even on Windows. We might be able to figure out
which `sh.exe` we would have to execute on Windows, but this is too
brittle. Windows users will have to use other mechanisms (like `$EDITOR`
and `$PAGER`) to configure their programs.
Command line parsing differs on Windows vs. POSIX compliant operating
systems (just consider the differences in handling `\` for paths vs. as
a marker for escape sequences). Thus, on Windows use `winsplit` to
split command arguments instead of `shlex` for Unix-like OSes.
`winsplit` is a small crate with no other dependencies.
Domain Names have restrictions on their total length, and the length
of individual labels[^0].
Since `TypeName`s are expected to be reverse domain name strings, the
total length of the internal string should not exceed 255 and each
component should not exceed 63.
[^0]: https://www.rfc-editor.org/rfc/rfc1035#section-2.3.4
The test name being used is `valid_typenames`, however it tests for
invalid `TypeName`s as well.
The invalid `TypeName` tests are now separated into another test
called `invalid_typenames`.
Enable the sha1 feature by default for `radicle-oid`.
Without this, the crate cannot be tested in isolation. It results in a
compile error, requiring the feature to be enabled.
Wire up the new `FetcherService`. This reduces the `Service` fetch
methods down to:
- `fetch`
- `fetched`
- `dequeue_fetches`
- `fetch_refs_at`
This simplifies the code by off-loading the logic to the `fetcher`
family of types, rather than handling the intricacies in the `Service`
itself.
The `Service` now just performs the necessary I/O based on the
returned events from the `fetcher`.
This also removes the fetching state from the node sessions. This
follows the single responsibility, and sessions now only care about
their connectivity.
Breaking changes:
- The `Connected` state of a peer no longer contains fetching
information, which was being returned as part of the JSON payload
result.
- The `rad debug` information for ongoing fetches contained the number
listeners awaiting for results, this was removed.
The `FetcherService` wraps the `FetcherState` alongside keeping track
of subscribers for a given fetch.
Typically, this is a channel for which the fetch result should be sent
to.
These subscribers are coalesced by maintaing a map from `(rid, node)`
pairs to the subscriber.
This continues the sans-IO approach where the type of subscribers are
not specified, but rather are kept as a type parameter – to be
specified by the caller.
This patch introduces the `FetcherState` which encapsulates the logic
and state for keeping track of fetches.
It uses a sans-IO approach, where the state of fetches transition
based on the current state and provided input.
Callers can then decide to perform I/O based on the returned event.
The `From<String> for Message` implementation has an interesting
interaction between `clap` and how `Message` can be used from the
command line.
Note that `FromStr` is not implemented for `Message` – which usually
what is to be expected for use with `clap`, but in fact, `clap` also
allows `From<String>`.
This would make `Message` be a required option when using `rad issue comment`.
It is not possible to use `default_value_t` because that required an
implementation of `Display`, which in this case we do not want to
implement. Trying to use `default_value = "Message::Edit"` also would
not work – it uses that as the text.
So, the solution is to mark it as optional, and default to
`Message::Edit` when it is not specified.
Be more defensive by preventing a node, that is not configured for
Tor, to not connect to `.onion` addresses via the `Service::connect`
method.
This means that an `Outbox::connect` should not be produced, and the
address table should not insert an entry.
When maintaining persistent connections, the config was consulted for
a peer address. Multiple addresses for a single `NodeId` can be
listed. This can result in a different address being used for
reconnecting if the `HashSet` returns in a different order.
The original address that was used for connection is in the `Session`,
so this should be used instead.
This command is confusing in name and function. Rather than creating
a hard fork of a repository, with a new identity, it pushes the
default branch to the local user's namespace.
Deprecate it and add some help text.