mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Add toggle setting for LAN plugin.
This commit is contained in:
@@ -9,11 +9,11 @@ import android.net.wifi.WifiManager;
|
||||
|
||||
import org.briarproject.bramble.PoliteExecutor;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
@@ -36,10 +36,11 @@ import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.TcpConstants.PREF_TCP_ENABLE;
|
||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||
|
||||
@NotNullByDefault
|
||||
class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
|
||||
class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(AndroidLanTcpPlugin.class.getName());
|
||||
@@ -72,7 +73,8 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
|
||||
public void start() {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
initialisePortProperty();
|
||||
state.setStarted();
|
||||
Settings settings = callback.getSettings();
|
||||
state.setStarted(settings.getBoolean(PREF_TCP_ENABLE, false));
|
||||
updateConnectionStatus();
|
||||
}
|
||||
|
||||
@@ -128,6 +130,7 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
super.eventOccurred(e);
|
||||
if (e instanceof NetworkStatusEvent) updateConnectionStatus();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.briarproject.bramble.api.plugin;
|
||||
|
||||
public interface TcpConstants {
|
||||
|
||||
String PREF_TCP_ENABLE = "enable";
|
||||
|
||||
int REASON_USER = 1;
|
||||
}
|
||||
@@ -91,7 +91,8 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
public void start() {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
initialisePortProperty();
|
||||
running = true;
|
||||
Settings settings = callback.getSettings();
|
||||
state.setStarted(settings.getBoolean(PREF_PLUGIN_ENABLE, false));
|
||||
bind();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.plugin.tcp;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||
@@ -26,11 +27,13 @@ public class LanTcpPluginFactory implements DuplexPluginFactory {
|
||||
private static final double BACKOFF_BASE = 1.2;
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final EventBus eventBus;
|
||||
private final BackoffFactory backoffFactory;
|
||||
|
||||
public LanTcpPluginFactory(Executor ioExecutor,
|
||||
public LanTcpPluginFactory(Executor ioExecutor, EventBus eventBus,
|
||||
BackoffFactory backoffFactory) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.eventBus = eventBus;
|
||||
this.backoffFactory = backoffFactory;
|
||||
}
|
||||
|
||||
@@ -48,7 +51,9 @@ public class LanTcpPluginFactory implements DuplexPluginFactory {
|
||||
public DuplexPlugin createPlugin(PluginCallback callback) {
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
return new LanTcpPlugin(ioExecutor, backoff, callback, MAX_LATENCY,
|
||||
LanTcpPlugin plugin = new LanTcpPlugin(ioExecutor, backoff, callback, MAX_LATENCY,
|
||||
MAX_IDLE_TIME, CONNECTION_TIMEOUT);
|
||||
eventBus.addListener(plugin);
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ package org.briarproject.bramble.plugin.tcp;
|
||||
import org.briarproject.bramble.PoliteExecutor;
|
||||
import org.briarproject.bramble.api.Pair;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
@@ -15,6 +18,8 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
|
||||
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
@@ -46,6 +51,8 @@ import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.TcpConstants.PREF_TCP_ENABLE;
|
||||
import static org.briarproject.bramble.api.plugin.TcpConstants.REASON_USER;
|
||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
|
||||
@@ -53,7 +60,7 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
abstract class TcpPlugin implements DuplexPlugin {
|
||||
abstract class TcpPlugin implements DuplexPlugin, EventListener {
|
||||
|
||||
private static final Logger LOG = getLogger(TcpPlugin.class.getName());
|
||||
|
||||
@@ -122,7 +129,8 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
@Override
|
||||
public void start() {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
state.setStarted();
|
||||
Settings settings = callback.getSettings();
|
||||
state.setStarted(settings.getBoolean(PREF_TCP_ENABLE, false));
|
||||
bind();
|
||||
}
|
||||
|
||||
@@ -200,7 +208,7 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
|
||||
@Override
|
||||
public int getReasonDisabled() {
|
||||
return getState() == DISABLED ? REASON_STARTING_STOPPING : -1;
|
||||
return state.getReasonDisabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -370,18 +378,44 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof SettingsUpdatedEvent) {
|
||||
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
|
||||
if (s.getNamespace().equals(getId().getString()))
|
||||
ioExecutor.execute(() -> onSettingsUpdated(s.getSettings()));
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void onSettingsUpdated(Settings settings) {
|
||||
boolean enabledByUser = settings.getBoolean(PREF_TCP_ENABLE, false);
|
||||
ServerSocket ss = state.setEnabledByUser(enabledByUser);
|
||||
State s = getState();
|
||||
callback.pluginStateChanged(s);
|
||||
if (ss != null) {
|
||||
LOG.info("Disabled by user, closing server socket");
|
||||
tryToClose(ss, LOG, WARNING);
|
||||
} else if (s == INACTIVE) {
|
||||
LOG.info("Enabled by user, opening server socket");
|
||||
bind();
|
||||
}
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
protected class PluginState {
|
||||
|
||||
@GuardedBy("this")
|
||||
private boolean started = false, stopped = false;
|
||||
private boolean started = false, stopped = false, enabledByUser = false;
|
||||
|
||||
@GuardedBy("this")
|
||||
@Nullable
|
||||
private ServerSocket serverSocket = null;
|
||||
|
||||
synchronized void setStarted() {
|
||||
synchronized void setStarted(boolean enabledByUser) {
|
||||
started = true;
|
||||
this.enabledByUser = enabledByUser;
|
||||
callback.pluginStateChanged(getState());
|
||||
}
|
||||
|
||||
@@ -394,6 +428,18 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
return ss;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
synchronized ServerSocket setEnabledByUser(boolean enabledByUser) {
|
||||
this.enabledByUser = enabledByUser;
|
||||
ServerSocket ss = null;
|
||||
if (!enabledByUser) {
|
||||
ss = serverSocket;
|
||||
serverSocket = null;
|
||||
}
|
||||
callback.pluginStateChanged(getState());
|
||||
return ss;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
synchronized ServerSocket getServerSocket() {
|
||||
return serverSocket;
|
||||
@@ -412,8 +458,13 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
}
|
||||
|
||||
synchronized State getState() {
|
||||
if (!started || stopped) return DISABLED;
|
||||
if (!started || stopped || !enabledByUser) return DISABLED;
|
||||
return serverSocket == null ? INACTIVE : ACTIVE;
|
||||
}
|
||||
|
||||
synchronized int getReasonDisabled() {
|
||||
if (!started || stopped) return REASON_STARTING_STOPPING;
|
||||
return enabledByUser ? -1 : REASON_USER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.plugin.tcp;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.lifecycle.ShutdownManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
@@ -27,12 +28,14 @@ public class WanTcpPluginFactory implements DuplexPluginFactory {
|
||||
private static final double BACKOFF_BASE = 1.2;
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final EventBus eventBus;
|
||||
private final BackoffFactory backoffFactory;
|
||||
private final ShutdownManager shutdownManager;
|
||||
|
||||
public WanTcpPluginFactory(Executor ioExecutor,
|
||||
public WanTcpPluginFactory(Executor ioExecutor, EventBus eventBus,
|
||||
BackoffFactory backoffFactory, ShutdownManager shutdownManager) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.eventBus = eventBus;
|
||||
this.backoffFactory = backoffFactory;
|
||||
this.shutdownManager = shutdownManager;
|
||||
}
|
||||
@@ -51,8 +54,10 @@ public class WanTcpPluginFactory implements DuplexPluginFactory {
|
||||
public DuplexPlugin createPlugin(PluginCallback callback) {
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
return new WanTcpPlugin(ioExecutor, backoff,
|
||||
WanTcpPlugin plugin = new WanTcpPlugin(ioExecutor, backoff,
|
||||
new PortMapperImpl(shutdownManager), callback, MAX_LATENCY,
|
||||
MAX_IDLE_TIME, CONNECTION_TIMEOUT);
|
||||
eventBus.addListener(plugin);
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +45,9 @@ public class DesktopPluginModule extends PluginModule {
|
||||
ioExecutor, random, eventBus, timeoutMonitor, backoffFactory);
|
||||
DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor,
|
||||
reliabilityFactory);
|
||||
DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor,
|
||||
DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor, eventBus,
|
||||
backoffFactory);
|
||||
DuplexPluginFactory wan = new WanTcpPluginFactory(ioExecutor,
|
||||
DuplexPluginFactory wan = new WanTcpPluginFactory(ioExecutor, eventBus,
|
||||
backoffFactory, shutdownManager);
|
||||
Collection<DuplexPluginFactory> duplex =
|
||||
asList(bluetooth, modem, lan, wan);
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.BluetoothConstants;
|
||||
import org.briarproject.bramble.api.plugin.LanTcpConstants;
|
||||
import org.briarproject.bramble.api.plugin.TorConstants;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
@@ -73,6 +74,7 @@ import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_LTR;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
|
||||
import static org.briarproject.bramble.api.plugin.TcpConstants.PREF_TCP_ENABLE;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ENABLE;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
|
||||
@@ -116,6 +118,9 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
BluetoothConstants.ID.getString();
|
||||
private static final String BT_ENABLE = "pref_key_bluetooth";
|
||||
|
||||
private static final String WIFI_NAMESPACE = LanTcpConstants.ID.getString();
|
||||
private static final String WIFI_ENABLE = "pref_key_wifi";
|
||||
|
||||
private static final String TOR_NAMESPACE = TorConstants.ID.getString();
|
||||
private static final String TOR_ENABLE = "pref_key_tor_enable";
|
||||
private static final String TOR_NETWORK = "pref_key_tor_network";
|
||||
@@ -129,6 +134,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
private SettingsActivity listener;
|
||||
private ListPreference language;
|
||||
private SwitchPreference enableBluetooth;
|
||||
private SwitchPreference enableWifi;
|
||||
private SwitchPreference enableTor;
|
||||
private ListPreference torNetwork;
|
||||
private SwitchPreference torMobile;
|
||||
@@ -144,7 +150,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
private Preference notifySound;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
private volatile Settings settings, btSettings, torSettings;
|
||||
private volatile Settings settings, btSettings, wifiSettings, torSettings;
|
||||
private volatile boolean settingsLoaded = false;
|
||||
|
||||
@Inject
|
||||
@@ -174,6 +180,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
setLanguageEntries();
|
||||
ListPreference theme = findPreference("pref_key_theme");
|
||||
enableBluetooth = findPreference(BT_ENABLE);
|
||||
enableWifi = findPreference(WIFI_ENABLE);
|
||||
enableTor = findPreference(TOR_ENABLE);
|
||||
torNetwork = findPreference(TOR_NETWORK);
|
||||
torMobile = findPreference(TOR_MOBILE);
|
||||
@@ -207,6 +214,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
return true;
|
||||
});
|
||||
enableBluetooth.setOnPreferenceChangeListener(this);
|
||||
enableWifi.setOnPreferenceChangeListener(this);
|
||||
enableTor.setOnPreferenceChangeListener(this);
|
||||
torNetwork.setOnPreferenceChangeListener(this);
|
||||
torMobile.setOnPreferenceChangeListener(this);
|
||||
@@ -354,6 +362,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
long start = now();
|
||||
settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
|
||||
btSettings = settingsManager.getSettings(BT_NAMESPACE);
|
||||
wifiSettings = settingsManager.getSettings(WIFI_NAMESPACE);
|
||||
torSettings = settingsManager.getSettings(TOR_NAMESPACE);
|
||||
settingsLoaded = true;
|
||||
logDuration(LOG, "Loading settings", start);
|
||||
@@ -373,6 +382,10 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
btSettings.getBoolean(PREF_BT_ENABLE, false);
|
||||
enableBluetooth.setChecked(btEnabledSetting);
|
||||
|
||||
boolean wifiEnabledSetting =
|
||||
wifiSettings.getBoolean(PREF_TCP_ENABLE, false);
|
||||
enableWifi.setChecked(wifiEnabledSetting);
|
||||
|
||||
boolean torEnabledSetting =
|
||||
torSettings.getBoolean(PREF_TOR_ENABLE, true);
|
||||
enableTor.setChecked(torEnabledSetting);
|
||||
@@ -449,6 +462,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
// - pref_key_lock (screenLock -> displayScreenLockSetting())
|
||||
// - pref_key_lock_timeout (screenLockTimeout)
|
||||
enableBluetooth.setEnabled(enabled);
|
||||
enableWifi.setEnabled(enabled);
|
||||
enableTor.setEnabled(enabled);
|
||||
torNetwork.setEnabled(enabled && enableTor.isChecked());
|
||||
torMobile.setEnabled(enabled && enableTor.isChecked());
|
||||
@@ -553,7 +567,10 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
return false;
|
||||
} else if (preference == enableBluetooth) {
|
||||
boolean btSetting = (Boolean) newValue;
|
||||
storeBluetoothSettings(btSetting);
|
||||
storeBluetoothSetting(btSetting);
|
||||
} else if (preference == enableWifi) {
|
||||
boolean wifiSetting = (Boolean) newValue;
|
||||
storeWifiSetting(wifiSetting);
|
||||
} else if (preference == enableTor) {
|
||||
boolean torEnabledSetting = (Boolean) newValue;
|
||||
torNetwork.setEnabled(torNetwork.isEnabled() && torEnabledSetting);
|
||||
@@ -648,12 +665,18 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
mergeSettings(s, TOR_NAMESPACE);
|
||||
}
|
||||
|
||||
private void storeBluetoothSettings(boolean btSetting) {
|
||||
private void storeBluetoothSetting(boolean btSetting) {
|
||||
Settings s = new Settings();
|
||||
s.putBoolean(PREF_BT_ENABLE, btSetting);
|
||||
mergeSettings(s, BT_NAMESPACE);
|
||||
}
|
||||
|
||||
private void storeWifiSetting(boolean wifiSetting) {
|
||||
Settings s = new Settings();
|
||||
s.putBoolean(PREF_TCP_ENABLE, wifiSetting);
|
||||
mergeSettings(s, WIFI_NAMESPACE);
|
||||
}
|
||||
|
||||
private void storeSettings(Settings s) {
|
||||
mergeSettings(s, SETTINGS_NAMESPACE);
|
||||
}
|
||||
@@ -716,6 +739,10 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
LOG.info("Bluetooth settings updated");
|
||||
btSettings = s.getSettings();
|
||||
displaySettings();
|
||||
} else if (namespace.equals(WIFI_NAMESPACE)) {
|
||||
LOG.info("Wifi settings updated");
|
||||
wifiSettings = s.getSettings();
|
||||
displaySettings();
|
||||
} else if (namespace.equals(TOR_NAMESPACE)) {
|
||||
LOG.info("Tor settings updated");
|
||||
torSettings = s.getSettings();
|
||||
|
||||
@@ -446,6 +446,7 @@
|
||||
<!-- Settings Connections -->
|
||||
<string name="network_settings_title">Connections</string>
|
||||
<string name="bluetooth_setting">Connect to contacts via Bluetooth</string>
|
||||
<string name="wifi_setting">Connect to contacts on the same Wi-Fi network</string>
|
||||
<string name="tor_enable_title">Connect to contacts via Internet (Tor)</string>
|
||||
<string name="tor_network_setting">Connection method for Internet (Tor)</string>
|
||||
<string name="tor_network_setting_automatic">Automatic based on location</string>
|
||||
|
||||
@@ -37,6 +37,14 @@
|
||||
android:widgetLayout="@layout/preference_switch_compat"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="pref_key_wifi"
|
||||
android:persistent="false"
|
||||
android:title="@string/wifi_setting"
|
||||
android:widgetLayout="@layout/preference_switch_compat"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="pref_key_tor_enable"
|
||||
|
||||
Reference in New Issue
Block a user