summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/CustomListsRepository.kt5
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/CustomListActionUseCase.kt17
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/CustomListActionUseCaseTest.kt10
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt13
-rw-r--r--desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts9
-rw-r--r--desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts9
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/app.tsx5
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/select-location/CustomLists.tsx5
-rw-r--r--desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts2
-rw-r--r--desktop/packages/mullvad-vpn/src/shared/ipc-schema.ts3
-rw-r--r--mullvad-cli/src/cmds/bridge.rs2
-rw-r--r--mullvad-cli/src/cmds/custom_list.rs2
-rw-r--r--mullvad-cli/src/cmds/relay.rs4
-rw-r--r--mullvad-daemon/src/custom_list.rs16
-rw-r--r--mullvad-daemon/src/lib.rs15
-rw-r--r--mullvad-daemon/src/management_interface.rs18
-rw-r--r--mullvad-management-interface/proto/management_interface.proto7
-rw-r--r--mullvad-management-interface/src/client.rs10
-rw-r--r--mullvad-management-interface/src/types/conversions/custom_list.rs18
-rw-r--r--mullvad-relay-selector/src/relay_selector/matcher.rs2
-rw-r--r--mullvad-types/src/custom_list.rs34
-rw-r--r--mullvad-types/src/relay_constraints.rs2
-rw-r--r--test/test-manager/src/tests/mod.rs2
23 files changed, 143 insertions, 67 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/CustomListsRepository.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/CustomListsRepository.kt
index 32dd9a8e0e..6b8fd69ff8 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/CustomListsRepository.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/CustomListsRepository.kt
@@ -28,7 +28,10 @@ class CustomListsRepository(
.mapNotNull { it.customLists }
.stateIn(CoroutineScope(dispatcher), SharingStarted.Eagerly, null)
- suspend fun createCustomList(name: CustomListName) = managementService.createCustomList(name)
+ suspend fun createCustomList(
+ name: CustomListName,
+ locations: List<GeoLocationId> = emptyList(),
+ ) = managementService.createCustomList(name, locations)
suspend fun deleteCustomList(id: CustomListId) = managementService.deleteCustomList(id)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/CustomListActionUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/CustomListActionUseCase.kt
index b1326d57b9..da1b6fcb56 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/CustomListActionUseCase.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/CustomListActionUseCase.kt
@@ -10,10 +10,8 @@ import net.mullvad.mullvadvpn.compose.communication.Deleted
import net.mullvad.mullvadvpn.compose.communication.LocationsChanged
import net.mullvad.mullvadvpn.compose.communication.Renamed
import net.mullvad.mullvadvpn.lib.model.CreateCustomListError
-import net.mullvad.mullvadvpn.lib.model.CustomList
import net.mullvad.mullvadvpn.lib.model.DeleteCustomListError
import net.mullvad.mullvadvpn.lib.model.GetCustomListError
-import net.mullvad.mullvadvpn.lib.model.UpdateCustomListError
import net.mullvad.mullvadvpn.lib.model.UpdateCustomListLocationsError
import net.mullvad.mullvadvpn.lib.model.UpdateCustomListNameError
import net.mullvad.mullvadvpn.relaylist.getRelayItemsByCodes
@@ -54,23 +52,12 @@ class CustomListActionUseCase(
): Either<CreateWithLocationsError, Created> = either {
val customListId =
customListsRepository
- .createCustomList(action.name)
+ .createCustomList(action.name, action.locations)
.mapLeft(CreateWithLocationsError::Create)
.bind()
val locationNames =
if (action.locations.isNotEmpty()) {
- customListsRepository
- .updateCustomList(
- CustomList(
- id = customListId,
- name = action.name,
- locations = action.locations,
- )
- )
- .mapLeft(CreateWithLocationsError::UpdateCustomList)
- .bind()
-
relayListRepository.relayList
.firstOrNull()
?.getRelayItemsByCodes(action.locations)
@@ -129,8 +116,6 @@ sealed interface CreateWithLocationsError : CustomListActionError {
data class Create(val error: CreateCustomListError) : CreateWithLocationsError
- data class UpdateCustomList(val error: UpdateCustomListError) : CreateWithLocationsError
-
data object UnableToFetchRelayList : CreateWithLocationsError
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/CustomListActionUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/CustomListActionUseCaseTest.kt
index 03d46cbde0..d9a687362a 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/CustomListActionUseCaseTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/CustomListActionUseCaseTest.kt
@@ -62,12 +62,8 @@ class CustomListActionUseCaseTest {
undo = action.not(createdId),
)
.right()
- coEvery { mockCustomListsRepository.createCustomList(name) } returns createdId.right()
- coEvery {
- mockCustomListsRepository.updateCustomList(
- CustomList(createdId, name, listOf(locationId))
- )
- } returns Unit.right()
+ coEvery { mockCustomListsRepository.createCustomList(name, listOf(locationId)) } returns
+ createdId.right()
relayListFlow.value =
listOf(
RelayItem.Location.Country(
@@ -91,7 +87,7 @@ class CustomListActionUseCaseTest {
val locationId = GeoLocationId.Country("AB")
val action = CustomListAction.Create(name = name, locations = listOf(locationId))
val expectedError = CreateWithLocationsError.Create(CustomListAlreadyExists).left()
- coEvery { mockCustomListsRepository.createCustomList(name) } returns
+ coEvery { mockCustomListsRepository.createCustomList(name, listOf(locationId)) } returns
CustomListAlreadyExists.left()
// Act
diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt
index f306c9f833..cff4e2bd3e 100644
--- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt
+++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt
@@ -76,6 +76,7 @@ import net.mullvad.mullvadvpn.lib.model.DnsOptions as ModelDnsOptions
import net.mullvad.mullvadvpn.lib.model.DnsOptions
import net.mullvad.mullvadvpn.lib.model.DnsState as ModelDnsState
import net.mullvad.mullvadvpn.lib.model.DnsState
+import net.mullvad.mullvadvpn.lib.model.GeoLocationId
import net.mullvad.mullvadvpn.lib.model.GetAccountDataError
import net.mullvad.mullvadvpn.lib.model.GetAccountHistoryError
import net.mullvad.mullvadvpn.lib.model.GetDeviceListError
@@ -576,9 +577,17 @@ class ManagementService(
.mapEmpty()
suspend fun createCustomList(
- name: CustomListName
+ name: CustomListName,
+ locations: List<GeoLocationId> = emptyList(),
): Either<CreateCustomListError, CustomListId> =
- Either.catch { grpc.createCustomList(StringValue.of(name.value)) }
+ Either.catch {
+ grpc.createCustomList(
+ ManagementInterface.NewCustomList.newBuilder()
+ .setName(name.value)
+ .addAllLocations(locations.map(GeoLocationId::fromDomain))
+ .build()
+ )
+ }
.map { CustomListId(it.value) }
.mapLeftStatus {
when (it.status.code) {
diff --git a/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts b/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts
index 206070598c..35c1f775f4 100644
--- a/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts
+++ b/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts
@@ -24,6 +24,7 @@ import {
IRelayListWithEndpointData,
ISettings,
NewAccessMethodSetting,
+ NewCustomList,
ObfuscationSettings,
ObfuscationType,
RelaySettings,
@@ -45,6 +46,7 @@ import {
convertToCustomList,
convertToCustomProxy,
convertToNewApiAccessMethodSetting,
+ convertToNewCustomList,
convertToNormalBridgeSettings,
convertToRelayConstraints,
ensureExists,
@@ -573,9 +575,12 @@ export class DaemonRpc extends GrpcClient {
await this.call<grpcTypes.DeviceRemoval, Empty>(this.client.removeDevice, grpcDeviceRemoval);
}
- public async createCustomList(name: string): Promise<void | CustomListError> {
+ public async createCustomList(newCustomList: NewCustomList): Promise<void | CustomListError> {
try {
- await this.callString<Empty>(this.client.createCustomList, name);
+ await this.call<grpcTypes.NewCustomList, StringValue>(
+ this.client.createCustomList,
+ convertToNewCustomList(newCustomList),
+ );
} catch (e) {
const error = e as grpc.ServiceError;
if (error.code === 6) {
diff --git a/desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts b/desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts
index 64c805c3b9..c5c6ccf4d2 100644
--- a/desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts
+++ b/desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts
@@ -47,6 +47,7 @@ import {
LoggedInDeviceState,
LoggedOutDeviceState,
NewAccessMethodSetting,
+ NewCustomList,
ObfuscationSettings,
ObfuscationType,
Ownership,
@@ -1345,6 +1346,14 @@ function convertFromSocksAuth(auth: grpcTypes.SocksAuth): SocksAuth {
};
}
+export function convertToNewCustomList(customList: NewCustomList): grpcTypes.NewCustomList {
+ const newCustomList = new grpcTypes.NewCustomList();
+ newCustomList.setName(customList.name);
+ const locations = customList.locations.map(convertToGeographicConstraint);
+ newCustomList.setLocationsList(locations);
+ return newCustomList;
+}
+
export function ensureExists<T>(value: T | undefined, errorMessage: string): T {
if (value) {
return value;
diff --git a/desktop/packages/mullvad-vpn/src/renderer/app.tsx b/desktop/packages/mullvad-vpn/src/renderer/app.tsx
index 7ceab22c2f..8c39327f40 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/app.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/app.tsx
@@ -29,6 +29,7 @@ import {
ISettings,
liftConstraint,
NewAccessMethodSetting,
+ NewCustomList,
ObfuscationSettings,
RelaySettings,
TunnelState,
@@ -408,8 +409,8 @@ export default class AppRenderer {
public getPathBaseName = (path: string) => IpcRendererEventChannel.app.getPathBaseName(path);
public showOpenDialog = (options: Electron.OpenDialogOptions) =>
IpcRendererEventChannel.app.showOpenDialog(options);
- public createCustomList = (name: string) =>
- IpcRendererEventChannel.customLists.createCustomList(name);
+ public createCustomList = (newCustomList: NewCustomList) =>
+ IpcRendererEventChannel.customLists.createCustomList(newCustomList);
public deleteCustomList = (id: string) =>
IpcRendererEventChannel.customLists.deleteCustomList(id);
public updateCustomList = (customList: ICustomList) =>
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/select-location/CustomLists.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/select-location/CustomLists.tsx
index c16badd158..cd535dd91e 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/select-location/CustomLists.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/select-location/CustomLists.tsx
@@ -81,7 +81,10 @@ export default function CustomLists(props: CustomListsProps) {
const createList = useCallback(
async (name: string): Promise<void | CustomListError> => {
- const result = await createCustomList(name);
+ const result = await createCustomList({
+ name,
+ locations: [],
+ });
// If an error is returned it should be passed as the return value.
if (result) {
return result;
diff --git a/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts b/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts
index b00fe4587a..45ed498237 100644
--- a/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts
+++ b/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts
@@ -474,6 +474,8 @@ export interface ICustomList {
locations: Array<RelayLocationGeographical>;
}
+export type NewCustomList = Pick<ICustomList, 'name' | 'locations'>;
+
export type CustomListError = { type: 'name already exists' };
export interface ISettings {
diff --git a/desktop/packages/mullvad-vpn/src/shared/ipc-schema.ts b/desktop/packages/mullvad-vpn/src/shared/ipc-schema.ts
index a85f55d18a..031d1fc593 100644
--- a/desktop/packages/mullvad-vpn/src/shared/ipc-schema.ts
+++ b/desktop/packages/mullvad-vpn/src/shared/ipc-schema.ts
@@ -20,6 +20,7 @@ import {
IRelayListWithEndpointData,
ISettings,
NewAccessMethodSetting,
+ NewCustomList,
ObfuscationSettings,
RelaySettings,
TunnelState,
@@ -147,7 +148,7 @@ export const ipcSchema = {
'': notifyRenderer<IRelayListWithEndpointData>(),
},
customLists: {
- createCustomList: invoke<string, void | CustomListError>(),
+ createCustomList: invoke<NewCustomList, void | CustomListError>(),
deleteCustomList: invoke<string, void>(),
updateCustomList: invoke<ICustomList, void | CustomListError>(),
},
diff --git a/mullvad-cli/src/cmds/bridge.rs b/mullvad-cli/src/cmds/bridge.rs
index 48458c9c1e..b5c436a893 100644
--- a/mullvad-cli/src/cmds/bridge.rs
+++ b/mullvad-cli/src/cmds/bridge.rs
@@ -155,7 +155,7 @@ impl Bridge {
let list =
super::custom_list::find_list_by_name(&mut rpc, &custom_list_name).await?;
let location =
- Constraint::Only(LocationConstraint::CustomList { list_id: list.id });
+ Constraint::Only(LocationConstraint::CustomList { list_id: list.id() });
Self::update_bridge_settings(&mut rpc, Some(location), None, None).await
}
SetCommands::Ownership { ownership } => {
diff --git a/mullvad-cli/src/cmds/custom_list.rs b/mullvad-cli/src/cmds/custom_list.rs
index edc6b4b0e2..06e177c634 100644
--- a/mullvad-cli/src/cmds/custom_list.rs
+++ b/mullvad-cli/src/cmds/custom_list.rs
@@ -160,7 +160,7 @@ impl CustomList {
async fn delete_list(name: String) -> Result<()> {
let mut rpc = MullvadProxyClient::new().await?;
let list = find_list_by_name(&mut rpc, &name).await?;
- rpc.delete_custom_list(list.id.to_string()).await?;
+ rpc.delete_custom_list(list.id()).await?;
Ok(())
}
diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs
index 43010862a9..30d041ea86 100644
--- a/mullvad-cli/src/cmds/relay.rs
+++ b/mullvad-cli/src/cmds/relay.rs
@@ -587,7 +587,7 @@ impl Relay {
let mut rpc = MullvadProxyClient::new().await?;
let list_id = super::custom_list::find_list_by_name(&mut rpc, &custom_list_name)
.await?
- .id;
+ .id();
Self::update_constraints(|constraints| {
constraints.location = Constraint::Only(LocationConstraint::CustomList { list_id });
})
@@ -685,7 +685,7 @@ impl Relay {
Some(EntryArgs::CustomList { custom_list_name }) => {
let list_id = super::custom_list::find_list_by_name(&mut rpc, &custom_list_name)
.await?
- .id;
+ .id();
wireguard_constraints.entry_location =
Constraint::Only(LocationConstraint::CustomList { list_id });
}
diff --git a/mullvad-daemon/src/custom_list.rs b/mullvad-daemon/src/custom_list.rs
index ba662a2dac..06f5926a2c 100644
--- a/mullvad-daemon/src/custom_list.rs
+++ b/mullvad-daemon/src/custom_list.rs
@@ -1,19 +1,27 @@
use crate::{Daemon, Error};
use mullvad_relay_selector::SelectorConfig;
+use mullvad_types::relay_constraints::GeographicLocationConstraint;
use mullvad_types::{
constraints::Constraint,
custom_list::{CustomList, Id},
relay_constraints::{BridgeState, LocationConstraint, RelaySettings, ResolvedBridgeSettings},
};
+use std::collections::BTreeSet;
use talpid_types::net::TunnelType;
impl Daemon {
/// Create a new custom list.
///
/// Returns an error if the name is not unique.
- pub async fn create_custom_list(&mut self, name: String) -> Result<Id, crate::Error> {
- let new_list = CustomList::new(name).map_err(crate::Error::CustomListError)?;
- let id = new_list.id;
+ pub async fn create_custom_list(
+ &mut self,
+ name: String,
+ locations: BTreeSet<GeographicLocationConstraint>,
+ ) -> Result<Id, crate::Error> {
+ let mut new_list = CustomList::new(name).map_err(crate::Error::CustomListError)?;
+ new_list.append(locations);
+
+ let id = new_list.id();
self.settings
.try_update(|settings| settings.custom_lists.add(new_list))
@@ -57,7 +65,7 @@ impl Daemon {
/// - there is no existing list with the same ID,
/// - or the existing list has a different name.
pub async fn update_custom_list(&mut self, new_list: CustomList) -> Result<(), Error> {
- let list_id = new_list.id;
+ let list_id = new_list.id();
let settings_changed = self
.settings
.try_update(|settings| settings.custom_lists.update(new_list))
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index f73ab6927c..5550ef3a8a 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -45,6 +45,7 @@ use mullvad_encrypted_dns_proxy::state::EncryptedDnsProxyState;
use mullvad_relay_selector::{RelaySelector, SelectorConfig};
#[cfg(target_os = "android")]
use mullvad_types::account::{PlayPurchase, PlayPurchasePaymentToken};
+use mullvad_types::relay_constraints::GeographicLocationConstraint;
#[cfg(any(windows, target_os = "android", target_os = "macos"))]
use mullvad_types::settings::SplitApp;
#[cfg(daita)]
@@ -70,6 +71,7 @@ use mullvad_types::{
};
use relay_list::{RelayListUpdater, RelayListUpdaterHandle, RELAYS_FILENAME};
use settings::SettingsPersister;
+use std::collections::BTreeSet;
#[cfg(any(windows, target_os = "android", target_os = "macos"))]
use std::collections::HashSet;
#[cfg(target_os = "android")]
@@ -310,7 +312,11 @@ pub enum DaemonCommand {
/// Return a public key of the currently set wireguard private key, if there is one
GetWireguardKey(ResponseTx<Option<PublicKey>, Error>),
/// Create custom list
- CreateCustomList(ResponseTx<mullvad_types::custom_list::Id, Error>, String),
+ CreateCustomList(
+ ResponseTx<mullvad_types::custom_list::Id, Error>,
+ String,
+ BTreeSet<GeographicLocationConstraint>,
+ ),
/// Delete custom list
DeleteCustomList(ResponseTx<(), Error>, mullvad_types::custom_list::Id),
/// Update a custom list with a given id
@@ -1442,7 +1448,9 @@ impl Daemon {
ResetSettings(tx) => self.on_reset_settings(tx).await,
RotateWireguardKey(tx) => self.on_rotate_wireguard_key(tx),
GetWireguardKey(tx) => self.on_get_wireguard_key(tx).await,
- CreateCustomList(tx, name) => self.on_create_custom_list(tx, name).await,
+ CreateCustomList(tx, name, locations) => {
+ self.on_create_custom_list(tx, name, locations).await
+ }
DeleteCustomList(tx, id) => self.on_delete_custom_list(tx, id).await,
UpdateCustomList(tx, update) => self.on_update_custom_list(tx, update).await,
ClearCustomLists(tx) => self.on_clear_custom_lists(tx).await,
@@ -2865,8 +2873,9 @@ impl Daemon {
&mut self,
tx: ResponseTx<mullvad_types::custom_list::Id, Error>,
name: String,
+ locations: BTreeSet<GeographicLocationConstraint>,
) {
- let result = self.create_custom_list(name).await;
+ let result = self.create_custom_list(name, locations).await;
Self::oneshot_send(tx, result, "create_custom_list response");
}
diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs
index 5695683fcf..aef1d17c45 100644
--- a/mullvad-daemon/src/management_interface.rs
+++ b/mullvad-daemon/src/management_interface.rs
@@ -4,10 +4,12 @@ use futures::{
StreamExt,
};
use mullvad_api::{rest::Error as RestError, StatusCode};
+use mullvad_management_interface::types::FromProtobufTypeError;
use mullvad_management_interface::{
types::{self, daemon_event, management_service_server::ManagementService},
Code, Request, Response, ServerJoinHandle, Status,
};
+use mullvad_types::relay_constraints::GeographicLocationConstraint;
use mullvad_types::{
account::AccountNumber,
relay_constraints::{
@@ -20,6 +22,7 @@ use mullvad_types::{
version,
wireguard::{RotationInterval, RotationIntervalError},
};
+use std::collections::BTreeSet;
use std::{
path::Path,
str::FromStr,
@@ -663,13 +666,22 @@ impl ManagementService for ManagementServiceImpl {
// Custom lists
//
- async fn create_custom_list(&self, request: Request<String>) -> ServiceResult<String> {
+ async fn create_custom_list(
+ &self,
+ request: Request<types::NewCustomList>,
+ ) -> ServiceResult<String> {
log::debug!("create_custom_list");
+ let request = request.into_inner();
+ let locations = request
+ .locations
+ .into_iter()
+ .map(GeographicLocationConstraint::try_from)
+ .collect::<Result<BTreeSet<_>, FromProtobufTypeError>>()?;
let (tx, rx) = oneshot::channel();
- self.send_command_to_daemon(DaemonCommand::CreateCustomList(tx, request.into_inner()))?;
+ self.send_command_to_daemon(DaemonCommand::CreateCustomList(tx, request.name, locations))?;
self.wait_for_result(rx)
.await?
- .map(|response| Response::new(response.to_string()))
+ .map(|id| Response::new(id.to_string()))
.map_err(map_daemon_error)
}
diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto
index ab9883c1ef..14a11fff4e 100644
--- a/mullvad-management-interface/proto/management_interface.proto
+++ b/mullvad-management-interface/proto/management_interface.proto
@@ -81,7 +81,7 @@ service ManagementService {
rpc GetWireguardKey(google.protobuf.Empty) returns (PublicKey) {}
// Custom lists
- rpc CreateCustomList(google.protobuf.StringValue) returns (google.protobuf.StringValue) {}
+ rpc CreateCustomList(NewCustomList) returns (google.protobuf.StringValue) {}
rpc DeleteCustomList(google.protobuf.StringValue) returns (google.protobuf.Empty) {}
rpc UpdateCustomList(CustomList) returns (google.protobuf.Empty) {}
rpc ClearCustomLists(google.protobuf.Empty) returns (google.protobuf.Empty) {}
@@ -442,6 +442,11 @@ message CustomList {
repeated GeographicLocationConstraint locations = 3;
}
+message NewCustomList {
+ string name = 1;
+ repeated GeographicLocationConstraint locations = 2;
+}
+
message CustomListSettings { repeated CustomList custom_lists = 1; }
message Socks5Local {
diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs
index 73129d6b99..93a31a5042 100644
--- a/mullvad-management-interface/src/client.rs
+++ b/mullvad-management-interface/src/client.rs
@@ -571,18 +571,22 @@ impl MullvadProxyClient {
}
pub async fn create_custom_list(&mut self, name: String) -> Result<Id> {
+ let request = types::NewCustomList {
+ name,
+ locations: Vec::new(),
+ };
let id = self
.0
- .create_custom_list(name)
+ .create_custom_list(request)
.await
.map_err(map_custom_list_error)?
.into_inner();
Id::from_str(&id).map_err(|_| Error::CustomListListNotFound)
}
- pub async fn delete_custom_list(&mut self, id: String) -> Result<()> {
+ pub async fn delete_custom_list(&mut self, id: Id) -> Result<()> {
self.0
- .delete_custom_list(id)
+ .delete_custom_list(id.to_string())
.await
.map_err(map_custom_list_error)?;
Ok(())
diff --git a/mullvad-management-interface/src/types/conversions/custom_list.rs b/mullvad-management-interface/src/types/conversions/custom_list.rs
index 9d4e9cee78..11f2c4bd76 100644
--- a/mullvad-management-interface/src/types/conversions/custom_list.rs
+++ b/mullvad-management-interface/src/types/conversions/custom_list.rs
@@ -30,13 +30,14 @@ impl TryFrom<proto::CustomListSettings> for mullvad_types::custom_list::CustomLi
impl From<mullvad_types::custom_list::CustomList> for proto::CustomList {
fn from(custom_list: mullvad_types::custom_list::CustomList) -> Self {
+ let id = custom_list.id().to_string();
let locations = custom_list
.locations
.into_iter()
.map(proto::GeographicLocationConstraint::from)
.collect();
Self {
- id: custom_list.id.to_string(),
+ id,
name: custom_list.name,
locations,
}
@@ -52,11 +53,14 @@ impl TryFrom<proto::CustomList> for mullvad_types::custom_list::CustomList {
.into_iter()
.map(GeographicLocationConstraint::try_from)
.collect::<Result<BTreeSet<_>, Self::Error>>()?;
- Ok(Self {
- id: Id::from_str(&custom_list.id)
- .map_err(|_| FromProtobufTypeError::InvalidArgument("Invalid list ID"))?,
- name: custom_list.name,
- locations,
- })
+
+ let id = Id::from_str(&custom_list.id)
+ .map_err(|_| FromProtobufTypeError::InvalidArgument("Invalid list ID"))?;
+
+ let mut inner = Self::with_id(id);
+ inner.name = custom_list.name;
+ inner.append(locations);
+
+ Ok(inner)
}
}
diff --git a/mullvad-relay-selector/src/relay_selector/matcher.rs b/mullvad-relay-selector/src/relay_selector/matcher.rs
index 88126137b0..e14885f2c4 100644
--- a/mullvad-relay-selector/src/relay_selector/matcher.rs
+++ b/mullvad-relay-selector/src/relay_selector/matcher.rs
@@ -235,7 +235,7 @@ impl<'a> ResolvedLocationConstraint<'a> {
}
LocationConstraint::CustomList { list_id } => custom_lists
.iter()
- .find(|list| list.id == *list_id)
+ .find(|list| list.id() == *list_id)
.map(|custom_list| {
ResolvedLocationConstraint(custom_list.locations.iter().collect())
})
diff --git a/mullvad-types/src/custom_list.rs b/mullvad-types/src/custom_list.rs
index 581c049be3..ed0176db50 100644
--- a/mullvad-types/src/custom_list.rs
+++ b/mullvad-types/src/custom_list.rs
@@ -20,7 +20,7 @@ pub enum Error {
ListExists,
}
-#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
+#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct Id(uuid::Uuid);
impl Deref for Id {
@@ -138,21 +138,41 @@ impl DerefMut for CustomListsSettings {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CustomList {
- pub id: Id,
+ id: Id,
pub name: String,
pub locations: BTreeSet<GeographicLocationConstraint>,
}
impl CustomList {
+ /// Create a new [CustomList] with a given name. This function will check that the name
+ /// is appropriate (see implementation for details) and generate a new unique [Id].
pub fn new(name: String) -> Result<Self, Error> {
if name.chars().count() > CUSTOM_LIST_NAME_MAX_SIZE {
return Err(Error::NameTooLong);
}
- Ok(CustomList {
- id: Id(uuid::Uuid::new_v4()),
- name,
- locations: BTreeSet::new(),
- })
+ let id = Id(uuid::Uuid::new_v4());
+ let mut custom_list = Self::with_id(id);
+ custom_list.name = name;
+
+ Ok(custom_list)
+ }
+
+ /// Instantiate an empty custom list with a pre-existing [Id]. This is useful when
+ /// serializing/deserializing, and most likely you want to use [CustomList::new] instead.
+ pub fn with_id(id: Id) -> Self {
+ Self {
+ id,
+ name: Default::default(),
+ locations: Default::default(),
+ }
+ }
+
+ pub fn id(&self) -> Id {
+ self.id
+ }
+
+ pub fn append(&mut self, mut locations: BTreeSet<GeographicLocationConstraint>) {
+ self.locations.append(&mut locations);
}
}
diff --git a/mullvad-types/src/relay_constraints.rs b/mullvad-types/src/relay_constraints.rs
index c0b6e31926..192386ddec 100644
--- a/mullvad-types/src/relay_constraints.rs
+++ b/mullvad-types/src/relay_constraints.rs
@@ -109,7 +109,7 @@ impl fmt::Display for LocationConstraintFormatter<'_> {
LocationConstraint::CustomList { list_id } => self
.custom_lists
.iter()
- .find(|list| &list.id == list_id)
+ .find(|list| list.id() == *list_id)
.map(|custom_list| write!(f, "{}", custom_list.name))
.unwrap_or_else(|| write!(f, "invalid custom list")),
}
diff --git a/test/test-manager/src/tests/mod.rs b/test/test-manager/src/tests/mod.rs
index 0858ffd9fa..fcf02ac0f6 100644
--- a/test/test-manager/src/tests/mod.rs
+++ b/test/test-manager/src/tests/mod.rs
@@ -191,7 +191,7 @@ pub async fn set_test_location(
let mut custom_list = find_custom_list(mullvad_client, &custom_list_name).await?;
- assert_eq!(list_id, custom_list.id);
+ assert_eq!(list_id, custom_list.id());
for location in locations {
custom_list.locations.insert(location);
}