Compare commits

...
9 Commits
21 changed files with 165 additions and 84 deletions
+3 -3
View File
@@ -2,7 +2,7 @@
<package >
<metadata>
<id>MQTTnet.AspNetCore</id>
<version>2.5.0</version>
<version>2.5.1</version>
<authors>Christian Kratky</authors>
<owners>Christian Kratky</owners>
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl>
@@ -10,13 +10,13 @@
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>This is a support library to integrate MQTTnet into AspNetCore.</description>
<releaseNotes>initial version
<releaseNotes>* Updated to MQTTnet 2.5.1.
</releaseNotes>
<copyright>Copyright Christian Kratky 2016-2017</copyright>
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
<dependencies>
<group targetFramework="netstandard2.0">
<dependency id="MQTTnet" version="2.5" />
<dependency id="MQTTnet" version="2.5.1" />
</group>
</dependencies>
</metadata>
+19 -18
View File
@@ -2,7 +2,7 @@
<package >
<metadata>
<id>MQTTnet</id>
<version>2.5.0</version>
<version>2.5.1</version>
<authors>Christian Kratky</authors>
<owners>Christian Kratky</owners>
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl>
@@ -10,22 +10,8 @@
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description>
<releaseNotes>* [Core] Merged the platform specific projects (Thanks to @JanEggers)
* [Core] Migrated the trace to a non-static approach (Breaking Change!)
* [Core] Added a builder for application messages using a fluent API
* [Core] Introduced CI and DI (Thanks to @JanEggers)
* [Core] Added interfaces for publishing and receiving which applies to every client and server (Thanks to @ChristianRiedl)
* [Core] Fixed an issue when reading from closed streams
* [Client] Added a first version of a managed client which will manage the connection, subscription etc. automatically (Thanks to @JTrotta)
* [Client] The session state response from the server is now returned in the _ConnectAsync_ method and also part of the _Connected_ event args
* [Client] Added a _TopicFilterBuilder_ using a fluent API (Namespace Changes!)
* [Client] Added several new options for the WebSocket channel (Thanks to @ChristianRiedl)
* [Client] Refactored the options and added a builder using a fluent API (Breaking Change!)
* [Client] Added more options for WebSocket connections like RequestHeaders, SubProtocol etc.
* [Server] Added support for WebSockets via ASP.NET Core 2.0 (additional nuget) (Thanks to @ChristianRiedl, @JanEggers)
* [Server] Added support for a custom application message interceptor
* [Server] Fixed an issue with dropped connections on UWP (Thanks to @haeberle)
* [Server] Added support for a custom subscription interceptor which allows denying of subscriptions and closing the connection
<releaseNotes>* Fixed several library reference issues.
* [Client] Fixed issue in _MqttClientOptionsBuilder_ when using no certificates (Thanks to 1iveowl).
</releaseNotes>
<copyright>Copyright Christian Kratky 2016-2017</copyright>
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
@@ -41,6 +27,17 @@
<dependency id="System.Net.WebSockets.Client" version="4.3.1" />
<dependency id="System.Threading.Thread" version="4.3.0" />
</group>
<group targetFramework="netstandard2.0">
<dependency id="NETStandard.Library" version="2.0.0" />
<dependency id="Microsoft.Extensions.DependencyInjection" version="2.0.0" />
<dependency id="Microsoft.Extensions.Logging" version="2.0.0" />
<dependency id="Microsoft.Extensions.Options" version="2.0.0" />
<dependency id="System.Net.Security" version="4.3.2" />
<dependency id="System.Net.WebSockets" version="4.3.0" />
<dependency id="System.Net.WebSockets.Client" version="4.3.1" />
<dependency id="System.Threading.Thread" version="4.3.0" />
</group>
<group targetFramework="uap10.0">
<dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="5.4.0" />
@@ -49,7 +46,7 @@
<dependency id="Microsoft.Extensions.Options" version="1.1.2" />
</group>
<group targetFramework="net451">
<group targetFramework="net452">
<dependency id="Microsoft.Extensions.DependencyInjection" version="1.1.1" />
<dependency id="Microsoft.Extensions.Logging" version="1.1.2" />
<dependency id="Microsoft.Extensions.Options" version="1.1.2" />
@@ -62,6 +59,10 @@
<!-- .NET Standard 1.3 -->
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\netstandard1.3\MQTTnet.Core.*" target="lib\netstandard1.3\"/>
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\netstandard1.3\MQTTnet.*" target="lib\netstandard1.3\"/>
<!-- .NET Standard 2.0 -->
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\netstandard2.0\MQTTnet.Core.*" target="lib\netstandard2.0\"/>
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\netstandard2.0\MQTTnet.*" target="lib\netstandard2.0\"/>
<!-- Universal Windows -->
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\uap10.0\MQTTnet.Core.*" target="lib\uap10.0\"/>
+1
View File
@@ -10,6 +10,7 @@ if ($path) {
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$version /p:AssemblyVersion=$version /verbosity:m
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$version /p:AssemblyVersion=$version /verbosity:m
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$version /p:AssemblyVersion=$version /verbosity:m
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$version /p:AssemblyVersion=$version /verbosity:m
&$msbuild ..\Frameworks\MQTTnet.AspNetCore\MQTTnet.AspNetCore.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$version /p:AssemblyVersion=$version /verbosity:m
@@ -1,4 +1,4 @@
#if NET452 || NETSTANDARD1_3
#if NET452 || NETSTANDARD1_3 || NETSTANDARD2_0
using System;
using System.Net;
using System.Net.Security;
@@ -1,4 +1,4 @@
#if NET452 || NETSTANDARD1_3
#if NET452 || NETSTANDARD1_3 || NETSTANDARD2_0
using System;
using System.Net.Security;
using System.Net.Sockets;
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard1.3;net452;uap10.0</TargetFrameworks>
<TargetFrameworks>netstandard1.3;netstandard2.0;net452;uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet</AssemblyName>
<RootNamespace>MQTTnet</RootNamespace>
<AssemblyVersion>2.5.0.0</AssemblyVersion>
@@ -32,23 +32,40 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\MQTTnet.Core\MQTTnet.Core.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
<PackageReference Include="System.Net.Security" Version="4.3.2" />
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" />
<PackageReference Include="System.Threading.Thread" Version="4.3.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="2.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
<PackageReference Include="System.Net.Security" Version="4.3.2" />
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" />
<PackageReference Include="System.Threading.Thread" Version="4.3.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='uap10.0'">
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="5.4.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net452'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
</ItemGroup>
</Project>
+14 -14
View File
@@ -16,20 +16,6 @@ namespace MQTTnet
{
private readonly IServiceProvider _serviceProvider;
private static IServiceProvider BuildServiceProvider()
{
var serviceProvider = new ServiceCollection()
.AddMqttClient()
.AddMqttServer()
.AddLogging()
.BuildServiceProvider();
serviceProvider.GetRequiredService<ILoggerFactory>()
.AddMqttTrace();
return serviceProvider;
}
public MqttFactory()
: this(BuildServiceProvider())
{
@@ -128,5 +114,19 @@ namespace MQTTnet
return _serviceProvider.GetRequiredService<IMqttServer>();
}
private static IServiceProvider BuildServiceProvider()
{
var serviceProvider = new ServiceCollection()
.AddMqttClient()
.AddMqttServer()
.AddLogging()
.BuildServiceProvider();
serviceProvider.GetRequiredService<ILoggerFactory>()
.AddMqttTrace();
return serviceProvider;
}
}
}
@@ -98,7 +98,7 @@ namespace MQTTnet.Core.Client
AllowUntrustedCertificates = allowUntrustedCertificates,
IgnoreCertificateChainErrors = ignoreCertificateChainErrors,
IgnoreCertificateRevocationErrors = ignoreCertificateRevocationErrors,
Certificates = certificates.ToList()
Certificates = certificates?.ToList()
};
return this;
+1
View File
@@ -8,6 +8,7 @@ namespace MQTTnet.Core.Server
{
event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected;
event EventHandler<MqttServerStartedEventArgs> Started;
IList<ConnectedMqttClient> GetConnectedClients();
void Publish(IEnumerable<MqttApplicationMessage> applicationMessages);
@@ -28,6 +28,11 @@ namespace MQTTnet.Core.Server
{
if (adapter == null) throw new ArgumentNullException(nameof(adapter));
if (cancellationToken.IsCancellationRequested)
{
return;
}
Task.Run(async () => await SendPendingPublishPacketsAsync(adapter, cancellationToken), cancellationToken).ConfigureAwait(false);
}
+9 -5
View File
@@ -60,12 +60,14 @@ namespace MQTTnet.Core.Server
try
{
var cancellationTokenSource = new CancellationTokenSource();
_willMessage = willMessage;
_adapter = adapter;
_cancellationTokenSource = new CancellationTokenSource();
_cancellationTokenSource = cancellationTokenSource;
_pendingMessagesQueue.Start(adapter, _cancellationTokenSource.Token);
await ReceivePacketsAsync(adapter, _cancellationTokenSource.Token).ConfigureAwait(false);
_pendingMessagesQueue.Start(adapter, cancellationTokenSource.Token);
await ReceivePacketsAsync(adapter, cancellationTokenSource.Token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -94,9 +96,11 @@ namespace MQTTnet.Core.Server
}
finally
{
if (_willMessage != null)
var willMessage = _willMessage;
if (willMessage != null)
{
_sessionsManager.DispatchApplicationMessage(this, _willMessage);
_willMessage = null; //clear willmessage so it is send just once
_sessionsManager.DispatchApplicationMessage(this, willMessage);
}
}
}
+3
View File
@@ -49,6 +49,7 @@ namespace MQTTnet.Core.Server
return _clientSessionsManager.GetConnectedClients();
}
public event EventHandler<MqttServerStartedEventArgs> Started;
public event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
public event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected;
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
@@ -96,6 +97,8 @@ namespace MQTTnet.Core.Server
}
_logger.LogInformation("Started.");
Started?.Invoke(this, new MqttServerStartedEventArgs());
}
public async Task StopAsync()
@@ -0,0 +1,8 @@
using System;
namespace MQTTnet.Core.Server
{
public class MqttServerStartedEventArgs : EventArgs
{
}
}
@@ -7,7 +7,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
<PackageReference Include="MSTest.TestAdapter" Version="1.2.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
@@ -15,7 +14,6 @@
<ItemGroup>
<ProjectReference Include="..\..\Frameworks\MQTTnet.Netstandard\MQTTnet.NetStandard.csproj" />
<ProjectReference Include="..\..\MQTTnet.Core\MQTTnet.Core.csproj" />
</ItemGroup>
</Project>
+30 -15
View File
@@ -9,6 +9,7 @@ using MQTTnet.Core.Client;
using MQTTnet.Core.Protocol;
using MQTTnet.Core.Server;
using Microsoft.Extensions.DependencyInjection;
using MQTTnet.Core.Internal;
using MQTTnet.Core.Packets;
namespace MQTTnet.Core.Tests
@@ -53,6 +54,7 @@ namespace MQTTnet.Core.Tests
public async Task MqttServer_WillMessage()
{
var serverAdapter = new TestMqttServerAdapter();
var services = new ServiceCollection()
.AddLogging()
.AddMqttServer()
@@ -70,16 +72,19 @@ namespace MQTTnet.Core.Tests
var c2 = await serverAdapter.ConnectTestClient(s, "c2", willMessage);
c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
await c1.SubscribeAsync(new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce));
await c1.SubscribeAsync(new TopicFilter("#"));
await c2.DisconnectAsync();
await Task.Delay(1000);
await c1.DisconnectAsync();
}
finally
{
await s.StopAsync();
}
Assert.AreEqual(1, receivedMessagesCount);
}
@@ -212,7 +217,6 @@ namespace MQTTnet.Core.Tests
.BuildServiceProvider();
var s = new MqttFactory(services).CreateMqttServer();
var retainMessagemanager = services.GetRequiredService<IMqttClientRetainedMessageManager>();
var receivedMessagesCount = 0;
try
@@ -223,19 +227,7 @@ namespace MQTTnet.Core.Tests
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
await c1.DisconnectAsync();
var subscribe = new MqttSubscribePacket()
{
TopicFilters = new List<TopicFilter>()
{
new TopicFilter("retained", MqttQualityOfServiceLevel.AtMostOnce)
}
};
//make shure the retainedMessageManagerreceived the package
while (!(await retainMessagemanager.GetSubscribedMessagesAsync(subscribe)).Any())
{
await Task.Delay(TimeSpan.FromMilliseconds(10));
}
await services.WaitForRetainedMessage("retained").TimeoutAfter(TimeSpan.FromSeconds(5));
var c2 = await serverAdapter.ConnectTestClient(s, "c2");
c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
@@ -314,6 +306,8 @@ namespace MQTTnet.Core.Tests
await s.StopAsync();
}
await services.WaitForRetainedMessage("retained").TimeoutAfter(TimeSpan.FromSeconds(5));
s = new MqttFactory(services).CreateMqttServer(options => options.Storage = storage);
var receivedMessagesCount = 0;
@@ -436,4 +430,25 @@ namespace MQTTnet.Core.Tests
Assert.AreEqual(expectedReceivedMessagesCount, receivedMessagesCount);
}
}
public static class TestExtensions
{
public static async Task WaitForRetainedMessage(this IServiceProvider services, string topic)
{
var retainMessagemanager = services.GetRequiredService<IMqttClientRetainedMessageManager>();
var subscribe = new MqttSubscribePacket()
{
TopicFilters = new List<TopicFilter>()
{
new TopicFilter(topic, MqttQualityOfServiceLevel.AtMostOnce)
}
};
while (!(await retainMessagemanager.GetSubscribedMessagesAsync(subscribe)).Any())
{
await Task.Delay(TimeSpan.FromMilliseconds(10));
}
}
}
}
@@ -33,7 +33,7 @@ namespace MQTTnet.Core.Tests
foreach (var packet in packets)
{
Partner.SendPacketInternal(packet);
Partner.EnqueuePacketInternal(packet);
}
return Task.FromResult(0);
@@ -46,7 +46,7 @@ namespace MQTTnet.Core.Tests
return Task.Run(() => _incomingPackets.Take(), cancellationToken);
}
private void SendPacketInternal(MqttBasePacket packet)
private void EnqueuePacketInternal(MqttBasePacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
@@ -4,11 +4,11 @@ using MQTTnet.Core.Client;
namespace MQTTnet.Core.Tests
{
public class MqttCommunicationAdapterFactory : IMqttCommunicationAdapterFactory
public class TestMqttCommunicationAdapterFactory : IMqttCommunicationAdapterFactory
{
private readonly IMqttCommunicationAdapter _adapter;
public MqttCommunicationAdapterFactory(IMqttCommunicationAdapter adapter)
public TestMqttCommunicationAdapterFactory(IMqttCommunicationAdapter adapter)
{
_adapter = adapter;
}
@@ -17,7 +17,11 @@ namespace MQTTnet.Core.Tests
adapterA.Partner = adapterB;
adapterB.Partner = adapterA;
var client = new MqttClient(new MqttCommunicationAdapterFactory(adapterA), new TestLogger<MqttClient>(), new MqttPacketDispatcher(new TestLogger<MqttPacketDispatcher>()));
var client = new MqttClient(
new TestMqttCommunicationAdapterFactory(adapterA),
new TestLogger<MqttClient>(),
new MqttPacketDispatcher(new TestLogger<MqttPacketDispatcher>()));
var connected = WaitForClientToConnect(server, clientId);
FireClientAcceptedEvent(adapterB);
@@ -29,8 +33,6 @@ namespace MQTTnet.Core.Tests
ChannelOptions = new MqttClientTcpOptions()
};
options.ChannelOptions = new MqttClientTcpOptions();
await client.ConnectAsync(options);
await connected;
+24 -12
View File
@@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using MQTTnet.AspNetCore;
using MQTTnet.Core;
@@ -21,21 +20,34 @@ namespace MQTTnet.TestApp.AspNetCore2
}
// In class _Startup_ of the ASP.NET Core 2.0 project.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMqttEndpoint();
app.UseMqttServer(async server =>
app.UseMqttServer(server =>
{
var msg = new MqttApplicationMessageBuilder()
.WithPayload("Mqtt is awesome")
.WithTopic("message");
while (true)
server.Started += async (sender, args) =>
{
server.PublishAsync(msg.Build()).Wait();
await Task.Delay(TimeSpan.FromSeconds(2));
msg.WithPayload("Mqtt is still awesome at " + DateTime.Now);
}
var msg = new MqttApplicationMessageBuilder()
.WithPayload("Mqtt is awesome")
.WithTopic("message");
while (true)
{
try
{
await server.PublishAsync(msg.Build());
msg.WithPayload("Mqtt is still awesome at " + DateTime.Now);
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
await Task.Delay(TimeSpan.FromSeconds(2));
}
}
};
});
app.Use((context, next) =>
@@ -7,13 +7,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Frameworks\MQTTnet.Netstandard\MQTTnet.NetStandard.csproj" />
<ProjectReference Include="..\..\MQTTnet.Core\MQTTnet.Core.csproj" />
</ItemGroup>
<ItemGroup>
@@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging;
using MQTTnet.Core;
using MQTTnet.Core.Client;
using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.ManagedClient;
using MQTTnet.Core.Protocol;
using MQTTnet.Core.Server;
using MQTTnet.Implementations;
@@ -454,6 +455,21 @@ namespace MQTTnet.TestApp.UniversalWindows
var mqttServer = new MqttFactory().CreateMqttServer();
}
{
// Setup and start a managed MQTT client.
var options = new ManagedMqttClientOptionsBuilder()
.WithAutoReconnectDelay(TimeSpan.FromSeconds(5))
.WithClientOptions(new MqttClientOptionsBuilder()
.WithClientId("Client1")
.WithTcpServer("broker.hivemq.com")
.WithTls().Build())
.Build();
var mqttClient = new MqttFactory().CreateManagedMqttClient();
await mqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic("my/topic").Build());
await mqttClient.StartAsync(options);
}
}
private async void StartServer(object sender, RoutedEventArgs e)