helper+model: GlobalRoutingHelper now interacts with NFD
This commit also changes uses of boost::tuple with std::tuple
diff --git a/helper/boost-graph-ndn-global-routing-helper.hpp b/helper/boost-graph-ndn-global-routing-helper.hpp
index 87d02f5..da96213 100644
--- a/helper/boost-graph-ndn-global-routing-helper.hpp
+++ b/helper/boost-graph-ndn-global-routing-helper.hpp
@@ -29,11 +29,12 @@
#include <boost/graph/properties.hpp>
#include <boost/ref.hpp>
-#include "ns3/ndn-face.hpp"
-#include "ns3/ndn-limits.hpp"
+#include "ns3/ndnSIM/model/ndn-face.hpp"
+#include "ns3/ndnSIM/model/ndn-global-router.hpp"
+
#include "ns3/node-list.h"
#include "ns3/channel-list.h"
-#include "../model/ndn-global-router.hpp"
+
#include <list>
#include <map>
@@ -103,13 +104,13 @@
inline graph_traits<NdnGlobalRouterGraph>::vertex_descriptor
source(graph_traits<NdnGlobalRouterGraph>::edge_descriptor e, const NdnGlobalRouterGraph& g)
{
- return e.get<0>();
+ return std::get<0>(e);
}
inline graph_traits<NdnGlobalRouterGraph>::vertex_descriptor
target(graph_traits<NdnGlobalRouterGraph>::edge_descriptor e, const NdnGlobalRouterGraph& g)
{
- return e.get<2>();
+ return std::get<2>(e);
}
inline std::pair<graph_traits<NdnGlobalRouterGraph>::vertex_iterator,
@@ -176,8 +177,8 @@
template<>
struct property_traits<EdgeWeights> {
// Metric property map
- typedef tuple<ns3::Ptr<ns3::ndn::Face>, uint16_t, double> value_type;
- typedef tuple<ns3::Ptr<ns3::ndn::Face>, uint16_t, double> reference;
+ typedef std::tuple<std::shared_ptr<nfd::Face>, uint16_t, double> value_type;
+ typedef std::tuple<std::shared_ptr<nfd::Face>, uint16_t, double> reference;
typedef ns3::ndn::GlobalRouter::Incidency key_type;
typedef readable_property_map_tag category;
};
@@ -189,16 +190,16 @@
struct WeightCompare : public std::binary_function<property_traits<EdgeWeights>::reference,
property_traits<EdgeWeights>::reference, bool> {
bool
- operator()(tuple<ns3::Ptr<ns3::ndn::Face>, uint32_t, double> a,
- tuple<ns3::Ptr<ns3::ndn::Face>, uint32_t, double> b) const
+ operator()(std::tuple<std::shared_ptr<nfd::Face>, uint32_t, double> a,
+ std::tuple<std::shared_ptr<nfd::Face>, uint32_t, double> b) const
{
- return a.get<1>() < b.get<1>();
+ return std::get<1>(a) < std::get<1>(b);
}
bool
operator()(property_traits<EdgeWeights>::reference a, uint32_t b) const
{
- return a.get<1>() < b;
+ return std::get<1>(a) < b;
}
bool
@@ -213,17 +214,19 @@
uint32_t
operator()(uint32_t a, property_traits<EdgeWeights>::reference b) const
{
- return a + b.get<1>();
+ return a + std::get<1>(b);
}
- tuple<ns3::Ptr<ns3::ndn::Face>, uint32_t, double>
- operator()(tuple<ns3::Ptr<ns3::ndn::Face>, uint32_t, double> a,
+ std::tuple<std::shared_ptr<nfd::Face>, uint32_t, double>
+ operator()(std::tuple<std::shared_ptr<nfd::Face>, uint32_t, double> a,
property_traits<EdgeWeights>::reference b) const
{
- if (a.get<0>() == 0)
- return make_tuple(b.get<0>(), a.get<1>() + b.get<1>(), a.get<2>() + b.get<2>());
+ if (std::get<0>(a) == 0)
+ return std::make_tuple(std::get<0>(b), std::get<1>(a) + std::get<1>(b),
+ std::get<2>(a) + std::get<2>(b));
else
- return make_tuple(a.get<0>(), a.get<1>() + b.get<1>(), a.get<2>() + b.get<2>());
+ return std::make_tuple(std::get<0>(a), std::get<1>(a) + std::get<1>(b),
+ std::get<2>(a) + std::get<2>(b));
}
};
@@ -267,17 +270,13 @@
inline property_traits<EdgeWeights>::reference
get(const boost::EdgeWeights&, ns3::ndn::GlobalRouter::Incidency& edge)
{
- if (edge.get<1>() == 0)
+ if (std::get<1>(edge) == 0)
return property_traits<EdgeWeights>::reference(0, 0, 0.0);
else {
- ns3::Ptr<ns3::ndn::Limits> limits = edge.get<1>()->GetObject<ns3::ndn::Limits>();
- double delay = 0.0;
- if (limits != 0) // valid limits object
- {
- delay = limits->GetLinkDelay();
- }
- return property_traits<EdgeWeights>::reference(edge.get<1>(), edge.get<1>()->GetMetric(),
- delay);
+ return property_traits<EdgeWeights>::reference(std::get<1>(edge),
+ static_cast<uint16_t>(
+ std::get<1>(edge)->getMetric()),
+ 0.0);
}
}
@@ -295,26 +294,25 @@
};
struct DistancesMap : public std::map<ns3::Ptr<ns3::ndn::GlobalRouter>,
- tuple<ns3::Ptr<ns3::ndn::Face>, uint32_t, double>> {
+ std::tuple<std::shared_ptr<nfd::Face>, uint32_t, double>> {
};
template<>
struct property_traits<reference_wrapper<DistancesMap>> {
// Metric property map
- typedef tuple<ns3::Ptr<ns3::ndn::Face>, uint32_t, double> value_type;
- typedef tuple<ns3::Ptr<ns3::ndn::Face>, uint32_t, double> reference;
+ typedef std::tuple<std::shared_ptr<nfd::Face>, uint32_t, double> value_type;
+ typedef std::tuple<std::shared_ptr<nfd::Face>, uint32_t, double> reference;
typedef ns3::Ptr<ns3::ndn::GlobalRouter> key_type;
typedef read_write_property_map_tag category;
};
-inline tuple<ns3::Ptr<ns3::ndn::Face>, uint32_t, double>
+inline std::tuple<std::shared_ptr<nfd::Face>, uint32_t, double>
get(DistancesMap& map, ns3::Ptr<ns3::ndn::GlobalRouter> key)
{
boost::DistancesMap::iterator i = map.find(key);
if (i == map.end())
- return tuple<ns3::Ptr<ns3::ndn::Face>, uint32_t, double>(0,
- std::numeric_limits<uint32_t>::max(),
- 0.0);
+ return std::tuple<std::shared_ptr<nfd::Face>, uint32_t,
+ double>(0, std::numeric_limits<uint32_t>::max(), 0.0);
else
return i->second;
}
diff --git a/helper/ndn-global-routing-helper.cpp b/helper/ndn-global-routing-helper.cpp
index 54b032b..43f0f20 100644
--- a/helper/ndn-global-routing-helper.cpp
+++ b/helper/ndn-global-routing-helper.cpp
@@ -26,10 +26,17 @@
#include "ndn-global-routing-helper.hpp"
-#include "ns3/ndn-l3-protocol.hpp"
-#include "../model/ndn-net-device-face.hpp"
-#include "../model/ndn-global-router.hpp"
+#include "model/ndn-l3-protocol.hpp"
+#include "helper/ndn-fib-helper.hpp"
+#include "model/ndn-net-device-face.hpp"
+#include "model/ndn-global-router.hpp"
+#include "daemon/table/fib.hpp"
+#include "daemon/fw/forwarder.hpp"
+#include "daemon/table/fib-entry.hpp"
+#include "daemon/table/fib-nexthop.hpp"
+
+#include "ns3/object.h"
#include "ns3/node.h"
#include "ns3/node-container.h"
#include "ns3/net-device.h"
@@ -44,8 +51,6 @@
#include <boost/lexical_cast.hpp>
#include <boost/foreach.hpp>
#include <boost/concept/assert.hpp>
-// #include <boost/graph/graph_concepts.hpp>
-// #include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include "boost-graph-ndn-global-routing-helper.hpp"
@@ -54,9 +59,6 @@
NS_LOG_COMPONENT_DEFINE("ndn.GlobalRoutingHelper");
-using namespace std;
-using namespace boost;
-
namespace ns3 {
namespace ndn {
@@ -77,8 +79,8 @@
gr = CreateObject<GlobalRouter>();
node->AggregateObject(gr);
- for (uint32_t faceId = 0; faceId < ndn->GetNFaces(); faceId++) {
- shared_ptr<NetDeviceFace> face = DynamicCast<NetDeviceFace>(ndn->GetFace(faceId));
+ for (auto& i : ndn->getForwarder()->getFaceTable()) {
+ shared_ptr<NetDeviceFace> face = std::dynamic_pointer_cast<NetDeviceFace>(i);
if (face == 0) {
NS_LOG_DEBUG("Skipping non-netdevice face");
continue;
@@ -203,7 +205,7 @@
{
for (NodeList::Iterator node = NodeList::Begin(); node != NodeList::End(); node++) {
Ptr<GlobalRouter> gr = (*node)->GetObject<GlobalRouter>();
- string name = Names::FindName(*node);
+ std::string name = Names::FindName(*node);
if (gr != 0 && !name.empty()) {
AddOrigin("/" + name, *node);
@@ -219,10 +221,10 @@
* See http://www.boost.org/doc/libs/1_49_0/libs/graph/doc/table_of_contents.html for more details
*/
- BOOST_CONCEPT_ASSERT((VertexListGraphConcept<NdnGlobalRouterGraph>));
- BOOST_CONCEPT_ASSERT((IncidenceGraphConcept<NdnGlobalRouterGraph>));
+ BOOST_CONCEPT_ASSERT((boost::VertexListGraphConcept<boost::NdnGlobalRouterGraph>));
+ BOOST_CONCEPT_ASSERT((boost::IncidenceGraphConcept<boost::NdnGlobalRouterGraph>));
- NdnGlobalRouterGraph graph;
+ boost::NdnGlobalRouterGraph graph;
// typedef graph_traits < NdnGlobalRouterGraph >::vertex_descriptor vertex_descriptor;
// For now we doing Dijkstra for every node. Can be replaced with Bellman-Ford or Floyd-Warshall.
@@ -236,56 +238,52 @@
continue;
}
- DistancesMap distances;
+ boost::DistancesMap distances;
dijkstra_shortest_paths(graph, source,
// predecessor_map (boost::ref(predecessors))
// .
distance_map(boost::ref(distances))
- .distance_inf(WeightInf)
- .distance_zero(WeightZero)
+ .distance_inf(boost::WeightInf)
+ .distance_zero(boost::WeightZero)
.distance_compare(boost::WeightCompare())
.distance_combine(boost::WeightCombine()));
// NS_LOG_DEBUG (predecessors.size () << ", " << distances.size ());
- /*
- Ptr<Fib> fib = source->GetObject<Fib> ();
- if (invalidatedRoutes)
- {
- fib->InvalidateAll ();
+
+ Ptr<L3Protocol> L3protocol = (*node)->GetObject<L3Protocol>();
+ shared_ptr<nfd::Forwarder> forwarder = L3protocol->getForwarder();
+
+ if (invalidatedRoutes) {
+ std::vector<::nfd::fib::NextHop> NextHopList;
+ for (nfd::Fib::const_iterator fibIt = forwarder->getFib().begin();
+ fibIt != forwarder->getFib().end();) {
+ NextHopList.clear();
+ NextHopList = fibIt->getNextHops();
+ ++fibIt;
+ for (int i = 0; i < NextHopList.size(); i++) {
+ NextHopList[i].setCost(std::numeric_limits<uint64_t>::max());
+ }
}
- NS_ASSERT (fib != 0);
-*/
+ }
+
NS_LOG_DEBUG("Reachability from Node: " << source->GetObject<Node>()->GetId());
- for (DistancesMap::iterator i = distances.begin(); i != distances.end(); i++) {
- if (i->first == source)
+ for (const auto& dist : distances) {
+ if (dist.first == source)
continue;
else {
- // cout << " Node " << i->first->GetObject<Node> ()->GetId ();
- if (i->second.get<0>() == 0) {
+ // cout << " Node " << dist.first->GetObject<Node> ()->GetId ();
+ if (std::get<0>(dist.second) == 0) {
// cout << " is unreachable" << endl;
}
else {
- BOOST_FOREACH (const std::shared_ptr<const Name>& prefix, i->first->GetLocalPrefixes()) {
- NS_LOG_DEBUG(" prefix " << prefix << " reachable via face " << *i->second.get<0>()
- << " with distance " << i->second.get<1>() << " with delay "
- << i->second.get<2>());
+ for (const auto& prefix : dist.first->GetLocalPrefixes()) {
+ NS_LOG_DEBUG(" prefix " << prefix << " reachable via face " << *std::get<0>(dist.second)
+ << " with distance " << std::get<1>(dist.second) << " with delay "
+ << std::get<2>(dist.second));
- // Ptr<fib::Entry> entry = fib->Add (prefix, i->second.get<0> (), i->second.get<1> ());
- // entry->SetRealDelayToProducer (i->second.get<0> (), Seconds (i->second.get<2> ()));
-
- // Ptr<Limits> faceLimits = i->second.get<0> ()->GetObject<Limits> ();
-
- // Ptr<Limits> fibLimits = entry->GetObject<Limits> ();
- // if (fibLimits != 0)
- //{
- // if it was created by the forwarding strategy via DidAddFibEntry event
- // fibLimits->SetLimits (faceLimits->GetMaxRate (), 2 * i->second.get<2> () /*exact
- // RTT*/);
- // NS_LOG_DEBUG ("Set limit for prefix " << *prefix << " " << faceLimits->GetMaxRate ()
- // << " / " << 2*i->second.get<2> () << "s (" << faceLimits->GetMaxRate () * 2 *
- // i->second.get<2> () << ")");
- //}
+ FibHelper::AddRoute(*node, *prefix, std::get<0>(dist.second),
+ std::get<1>(dist.second));
}
}
}
@@ -301,10 +299,10 @@
* See http://www.boost.org/doc/libs/1_49_0/libs/graph/doc/table_of_contents.html for more details
*/
- BOOST_CONCEPT_ASSERT((VertexListGraphConcept<NdnGlobalRouterGraph>));
- BOOST_CONCEPT_ASSERT((IncidenceGraphConcept<NdnGlobalRouterGraph>));
+ BOOST_CONCEPT_ASSERT((boost::VertexListGraphConcept<boost::NdnGlobalRouterGraph>));
+ BOOST_CONCEPT_ASSERT((boost::IncidenceGraphConcept<boost::NdnGlobalRouterGraph>));
- NdnGlobalRouterGraph graph;
+ boost::NdnGlobalRouterGraph graph;
// typedef graph_traits < NdnGlobalRouterGraph >::vertex_descriptor vertex_descriptor;
// For now we doing Dijkstra for every node. Can be replaced with Bellman-Ford or Floyd-Warshall.
@@ -318,13 +316,22 @@
continue;
}
- // Ptr<Fib> fib = source->GetObject<Fib> ();
- if (invalidatedRoutes) {
- // fib->InvalidateAll ();
- }
- // NS_ASSERT (fib != 0);
+ Ptr<L3Protocol> L3protocol = (*node)->GetObject<L3Protocol>();
+ shared_ptr<nfd::Forwarder> forwarder = L3protocol->getForwarder();
- NS_LOG_DEBUG("===========");
+ if (invalidatedRoutes) {
+ std::vector<::nfd::fib::NextHop> NextHopList;
+ for (nfd::Fib::const_iterator fibIt = forwarder->getFib().begin();
+ fibIt != forwarder->getFib().end();) {
+ NextHopList.clear();
+ NextHopList = fibIt->getNextHops();
+ ++fibIt;
+ for (int i = 0; i < NextHopList.size(); i++) {
+ NextHopList[i].setCost(std::numeric_limits<uint64_t>::max());
+ }
+ }
+ }
+
NS_LOG_DEBUG("Reachability from Node: " << source->GetObject<Node>()->GetId() << " ("
<< Names::FindName(source->GetObject<Node>()) << ")");
@@ -332,22 +339,29 @@
NS_ASSERT(l3 != 0);
// remember interface statuses
- std::vector<uint16_t> originalMetric(l3->GetNFaces());
- for (uint32_t faceId = 0; faceId < l3->GetNFaces(); faceId++) {
- originalMetric[faceId] = l3->GetFace(faceId)->GetMetric();
- l3->GetFace(faceId)
- ->SetMetric(std::numeric_limits<uint16_t>::max()
- - 1); // value std::numeric_limits<uint16_t>::max () MUST NOT be used (reserved)
+ int faceNumber = 0;
+ std::vector<uint16_t> originalMetric(uint32_t(l3->getForwarder()->getFaceTable().size()));
+ for (auto& i : l3->getForwarder()->getFaceTable()) {
+ faceNumber++;
+ shared_ptr<Face> nfdFace = std::dynamic_pointer_cast<Face>(i);
+ originalMetric[uint32_t(faceNumber)] = nfdFace->getMetric();
+ nfdFace->setMetric(std::numeric_limits<uint16_t>::max() - 1);
+ // value std::numeric_limits<uint16_t>::max () MUST NOT be used (reserved)
}
- for (uint32_t enabledFaceId = 0; enabledFaceId < l3->GetNFaces(); enabledFaceId++) {
- if (DynamicCast<ndn::NetDeviceFace>(l3->GetFace(enabledFaceId)) == 0)
+ faceNumber = 0;
+ for (auto& k : l3->getForwarder()->getFaceTable()) {
+ faceNumber++;
+ shared_ptr<NetDeviceFace> face = std::dynamic_pointer_cast<NetDeviceFace>(k);
+ if (face == 0) {
+ NS_LOG_DEBUG("Skipping non-netdevice face");
continue;
+ }
// enabling only faceId
- l3->GetFace(enabledFaceId)->SetMetric(originalMetric[enabledFaceId]);
+ face->setMetric(originalMetric[uint32_t(faceNumber)]);
- DistancesMap distances;
+ boost::DistancesMap distances;
NS_LOG_DEBUG("-----------");
@@ -355,59 +369,48 @@
// predecessor_map (boost::ref(predecessors))
// .
distance_map(boost::ref(distances))
- .distance_inf(WeightInf)
- .distance_zero(WeightZero)
+ .distance_inf(boost::WeightInf)
+ .distance_zero(boost::WeightZero)
.distance_compare(boost::WeightCompare())
.distance_combine(boost::WeightCombine()));
// NS_LOG_DEBUG (predecessors.size () << ", " << distances.size ());
- for (DistancesMap::iterator i = distances.begin(); i != distances.end(); i++) {
- if (i->first == source)
+ for (const auto& dist : distances) {
+ if (dist.first == source)
continue;
else {
- // cout << " Node " << i->first->GetObject<Node> ()->GetId ();
- if (i->second.get<0>() == 0) {
+ // cout << " Node " << dist.first->GetObject<Node> ()->GetId ();
+ if (std::get<0>(dist.second) == 0) {
// cout << " is unreachable" << endl;
}
else {
- BOOST_FOREACH (const std::shared_ptr<const Name>& prefix,
- i->first->GetLocalPrefixes()) {
- NS_LOG_DEBUG(" prefix " << *prefix << " reachable via face " << *i->second.get<0>()
- << " with distance " << i->second.get<1>() << " with delay "
- << i->second.get<2>());
+ for (const auto& prefix : dist.first->GetLocalPrefixes()) {
+ NS_LOG_DEBUG(" prefix " << *prefix << " reachable via face "
+ << *std::get<0>(dist.second)
+ << " with distance " << std::get<1>(dist.second)
+ << " with delay " << std::get<2>(dist.second));
- if (i->second.get<0>()->GetMetric() == std::numeric_limits<uint16_t>::max() - 1)
+ if (std::get<0>(dist.second)->getMetric() == std::numeric_limits<uint16_t>::max() - 1)
continue;
- // Ptr<fib::Entry> entry = fib->Add (prefix, i->second.get<0> (), i->second.get<1>
- // ());
- // entry->SetRealDelayToProducer (i->second.get<0> (), Seconds (i->second.get<2> ()));
-
- // Ptr<Limits> faceLimits = i->second.get<0> ()->GetObject<Limits> ();
-
- // Ptr<Limits> fibLimits = entry->GetObject<Limits> ();
- // if (fibLimits != 0)
- //{
- // if it was created by the forwarding strategy via DidAddFibEntry event
- // fibLimits->SetLimits (faceLimits->GetMaxRate (), 2 * i->second.get<2> () /*exact
- // RTT*/);
- // NS_LOG_DEBUG ("Set limit for prefix " << *prefix << " " << faceLimits->GetMaxRate
- // () << " / " << 2*i->second.get<2> () << "s (" << faceLimits->GetMaxRate () * 2 *
- // i->second.get<2> () << ")");
- //}
+ FibHelper::AddRoute(*node, *prefix, std::get<0>(dist.second),
+ std::get<1>(dist.second));
}
}
}
}
// disabling the face again
- l3->GetFace(enabledFaceId)->SetMetric(std::numeric_limits<uint16_t>::max() - 1);
+ face->setMetric(std::numeric_limits<uint16_t>::max() - 1);
}
// recover original interface statuses
- for (uint32_t faceId = 0; faceId < l3->GetNFaces(); faceId++) {
- l3->GetFace(faceId)->SetMetric(originalMetric[faceId]);
+ faceNumber = 0;
+ for (auto& i : l3->getForwarder()->getFaceTable()) {
+ faceNumber++;
+ shared_ptr<Face> face = std::dynamic_pointer_cast<Face>(i);
+ face->setMetric(originalMetric[faceNumber]);
}
}
}
diff --git a/model/ndn-global-router.cpp b/model/ndn-global-router.cpp
index b718689..634678f 100644
--- a/model/ndn-global-router.cpp
+++ b/model/ndn-global-router.cpp
@@ -20,8 +20,8 @@
#include "ndn-global-router.hpp"
-#include "ndn-l3-protocol.hpp"
-#include "ndn-face.hpp"
+#include "model/ndn-l3-protocol.hpp"
+#include "model/ndn-face.hpp"
#include "ns3/channel.h"
@@ -75,7 +75,7 @@
void
GlobalRouter::AddIncidency(shared_ptr<Face> face, Ptr<GlobalRouter> gr)
{
- m_incidencies.push_back(boost::make_tuple(this, face, gr));
+ m_incidencies.push_back(std::make_tuple(this, face, gr));
}
GlobalRouter::IncidencyList&
diff --git a/model/ndn-global-router.hpp b/model/ndn-global-router.hpp
index 0465cb8..ba7990d 100644
--- a/model/ndn-global-router.hpp
+++ b/model/ndn-global-router.hpp
@@ -28,7 +28,7 @@
#include "ns3/ptr.h"
#include <list>
-#include <boost/tuple/tuple.hpp>
+#include <tuple>
namespace ns3 {
@@ -47,7 +47,7 @@
/**
* @brief Graph edge
*/
- typedef boost::tuple<Ptr<GlobalRouter>, shared_ptr<Face>, Ptr<GlobalRouter>> Incidency;
+ typedef std::tuple<Ptr<GlobalRouter>, shared_ptr<Face>, Ptr<GlobalRouter>> Incidency;
/**
* @brief List of graph edges
*/
@@ -113,7 +113,7 @@
protected:
virtual void
NotifyNewAggregate(); ///< @brief Notify when the object is aggregated to another object (e.g.,
- /// Node)
+ /// Node)
private:
uint32_t m_id;
@@ -128,7 +128,8 @@
inline bool
operator==(const GlobalRouter::Incidency& a, const GlobalRouter::Incidency& b)
{
- return a.get<0>() == b.get<0>() && a.get<1>() == b.get<1>() && a.get<2>() == b.get<2>();
+ return std::get<0>(a) == std::get<0>(b) && std::get<1>(a) == std::get<1>(b)
+ && std::get<2>(a) == std::get<2>(b);
}
inline bool
diff --git a/wscript b/wscript
index e7012d6..97bfc0b 100644
--- a/wscript
+++ b/wscript
@@ -91,7 +91,8 @@
module.source += bld.path.ant_glob(['helper/ndn-face-container.cpp',
'helper/ndn-stack-helper.cpp',
'helper/ndn-fib-helper.cpp',
- 'helper/ndn-strategy-choice-helper.cpp'])
+ 'helper/ndn-strategy-choice-helper.cpp',
+ 'helper/ndn-global-routing-helper.cpp'])
module.full_headers = [p.path_from(bld.path) for p in bld.path.ant_glob(
['%s/**/*.hpp' % dir for dir in module_dirs])]