1.1 --- a/doc/manual/Makefile Fri Jan 30 19:28:00 2009 +0000
1.2 +++ b/doc/manual/Makefile Tue Feb 03 06:56:14 2009 -0800
1.3 @@ -33,6 +33,7 @@
1.4 callbacks.texi \
1.5 csma.texi \
1.6 emulation.texi \
1.7 + new-models.texi \
1.8 node.texi \
1.9 objects.texi \
1.10 other.texi \
2.1 --- a/doc/manual/manual.texi Fri Jan 30 19:28:00 2009 +0000
2.2 +++ b/doc/manual/manual.texi Tue Feb 03 06:56:14 2009 -0800
2.3 @@ -92,6 +92,7 @@
2.4 * Wifi NetDevice::
2.5 * CSMA NetDevice::
2.6 * PointToPoint NetDevice::
2.7 +* Creating a new ns-3 model::
2.8 * Troubleshooting::
2.9 @end menu
2.10
2.11 @@ -111,6 +112,7 @@
2.12 @include csma.texi
2.13 @include point-to-point.texi
2.14 @c @include other.texi
2.15 +@include new-models.texi
2.16 @include troubleshoot.texi
2.17
2.18 @printindex cp
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3.2 +++ b/doc/manual/new-models.texi Tue Feb 03 06:56:14 2009 -0800
3.3 @@ -0,0 +1,578 @@
3.4 +@node Creating a new ns-3 model
3.5 +@chapter Creating a new ns-3 model
3.6 +
3.7 +This chapter walks through the design process of an @command{ns-3} model.
3.8 +In many research cases, users will not be satisfied to merely adapt
3.9 +existing models, but may want to extend the core of the simulator in
3.10 +a novel way. We will use the example of adding an ErrorModel to
3.11 +a simple @command{ns-3} link as a motivating example of how one might
3.12 +approach this problem and proceed through a design and implementation.
3.13 +
3.14 +@node Design-approach
3.15 +@section Design-approach
3.16 +
3.17 +Consider how you want it to work; what should it do. Think about these
3.18 +things:
3.19 +@itemize @bullet
3.20 +@item @emph{functionality:} What functionality should it have? What
3.21 +attributes or configuration is exposed to the user?
3.22 +@item @emph{reusability:} How much should others be able to reuse my design?
3.23 +Can I reuse code from ns-2 to get started? How does a user integrate
3.24 +the model with the rest of another simulation?
3.25 +@item @emph{dependencies:} How can I reduce the introduction of
3.26 +outside dependencies on my new code as much as possible (to make it
3.27 +more modular)? For instance, should I avoid any dependence on IPv4 if
3.28 +I want it to also be used by IPv6? Should I avoid any dependency
3.29 +on IP at all?
3.30 +@end itemize
3.31 +
3.32 +Do not be hesitant to contact the ns-3-users or ns-developers list if
3.33 +you have questions. In particular, it is important to think about
3.34 +the public API of your new model and ask for feedback. It also helps
3.35 +to let others know of your work in case you are interested in
3.36 +collaborators.
3.37 +
3.38 +@subsection Example: ErrorModel
3.39 +
3.40 +An error model exists in ns-2. It allows packets to be passed to
3.41 +a stateful object that determines, based on a random
3.42 +variable, whether the packet is corrupted. The caller can then
3.43 +decide what to do with the packet (drop it, etc.).
3.44 +
3.45 +The main API of the error model is a function to pass a packet
3.46 +to, and the return value of this function is a boolean that
3.47 +tells the caller whether any corruption occurred. Note that
3.48 +depending on the error model, the packet data buffer may or may
3.49 +not be corrupted. Let's call this function "IsCorrupt()".
3.50 +
3.51 +So far, in our design, we have:
3.52 +@verbatim
3.53 +class ErrorModel
3.54 +{
3.55 +public:
3.56 + /**
3.57 + * \returns true if the Packet is to be considered as errored/corrupted
3.58 + * \param pkt Packet to apply error model to
3.59 + */
3.60 + bool IsCorrupt (Ptr<Packet> pkt);
3.61 +};
3.62 +@end verbatim
3.63 +Note that we do not pass a const pointer, thereby allowing the function
3.64 +to modify the packet if IsCorrupt() returns true. Not all error models
3.65 +will actually modify the packet; whether or not the packet data
3.66 +buffer is corrupted should be documented.
3.67 +
3.68 +We may also want specialized versions of this, such as in ns-2, so
3.69 +although it is not the only design choice for polymorphism, we assume
3.70 +that we will subclass a base class ErrorModel for specialized classes,
3.71 +such as RateErrorModel, ListErrorModel, etc, such as is done in ns-2.
3.72 +
3.73 +You may be thinking at this point, "Why not make IsCorrupt() a virtual
3.74 +method?". That is one approach; the other is to make the public
3.75 +non-virtual function indirect through a private virtual function
3.76 +(this in C++ is known as the non virtual interface idiom and is
3.77 +adopted in the ns-3 ErrorModel class).
3.78 +
3.79 +Next, should this device have any dependencies on IP or other protocols?
3.80 +We do not want to create dependencies on Internet protocols (the error
3.81 +model should be applicable to non-Internet protocols too), so we'll keep
3.82 +that in mind later.
3.83 +
3.84 +Another consideration is how objects will include this error model.
3.85 +We envision putting an explicit setter in certain NetDevice implementations,
3.86 +for example.
3.87 +@verbatim
3.88 + /**
3.89 + * Attach a receive ErrorModel to the PointToPointNetDevice.
3.90 + *
3.91 + * The PointToPointNetDevice may optionally include an ErrorModel in
3.92 + * the packet receive chain.
3.93 + *
3.94 + * @see ErrorModel
3.95 + * @param em Ptr to the ErrorModel.
3.96 + */
3.97 + void PointToPointNetDevice::SetReceiveErrorModel(Ptr<ErrorModel> em);
3.98 +@end verbatim
3.99 +Again, this is not the only choice we have (error models could be aggregated
3.100 +to lots of other objects), but it satisfies our primary use case, which
3.101 +is to allow a user to force errors on otherwise successful packet
3.102 +transmissions, at the NetDevice level.
3.103 +
3.104 +After some thinking and looking at existing ns-2 code, here is a sample
3.105 +API of a base class and first subclass that could be posted for initial
3.106 +review:
3.107 +@verbatim
3.108 +class ErrorModel
3.109 +{
3.110 +public:
3.111 + ErrorModel ();
3.112 + virtual ~ErrorModel ();
3.113 + bool IsCorrupt (Ptr<Packet> pkt);
3.114 + void Reset (void);
3.115 + void Enable (void);
3.116 + void Disable (void);
3.117 + bool IsEnabled (void) const;
3.118 +private:
3.119 + virtual bool DoCorrupt (Ptr<Packet> pkt) = 0;
3.120 + virtual void DoReset (void) = 0;
3.121 +};
3.122 +
3.123 +enum ErrorUnit
3.124 + {
3.125 + EU_BIT,
3.126 + EU_BYTE,
3.127 + EU_PKT
3.128 + };
3.129 +
3.130 +// Determine which packets are errored corresponding to an underlying
3.131 +// random variable distribution, an error rate, and unit for the rate.
3.132 +class RateErrorModel : public ErrorModel
3.133 +{
3.134 +public:
3.135 + RateErrorModel ();
3.136 + virtual ~RateErrorModel ();
3.137 + enum ErrorUnit GetUnit (void) const;
3.138 + void SetUnit (enum ErrorUnit error_unit);
3.139 + double GetRate (void) const;
3.140 + void SetRate (double rate);
3.141 + void SetRandomVariable (const RandomVariable &ranvar);
3.142 +private:
3.143 + virtual bool DoCorrupt (Ptr<Packet> pkt);
3.144 + virtual void DoReset (void);
3.145 +};
3.146 +@end verbatim
3.147 +
3.148 +@node Scaffolding
3.149 +@section Scaffolding
3.150 +
3.151 +Let's say that you are ready to start implementing; you have a fairly clear
3.152 +picture of what you want to build, and you may have solicited some
3.153 +initial review or suggestions from the list.
3.154 +One way to approach the next step (implementation) is to create scaffolding
3.155 +and fill in the details as the design matures.
3.156 +
3.157 +This section walks through many of the steps you should consider to
3.158 +define scaffolding, or a non-functional skeleton of what your model
3.159 +will eventually implement. It is usually good practice to not wait
3.160 +to get these details integrated at the end, but instead to plumb a
3.161 +skeleton of your model into the system early and then add functions
3.162 +later once the API and integration seems about right.
3.163 +
3.164 +Note that you will want to modify a few things in the below presentation
3.165 +for your model since if you follow the error model verbatim, the code
3.166 +you produce will collide with the existing error model. The below is
3.167 +just an outline of how ErrorModel was built that you can adapt to
3.168 +other models.
3.169 +
3.170 +@subsection Review the ns-3 coding style document
3.171 +
3.172 +At this point, you may want to pause and read the ns-3 coding
3.173 +style document, especially if you are considering to contribute your
3.174 +code back to the project. The coding style document is linked off
3.175 +the main project page:
3.176 +@uref{http://www.nsnam.org/codingstyle.html,,ns-3 coding style}.
3.177 +
3.178 +@subsection Decide where in the source tree the model will reside in
3.179 +
3.180 +All of the ns-3 model source code is in the directory @code{src/}.
3.181 +You will need to choose which subdirectory it resides in. If it is
3.182 +new model code of some sort, it makes sense to put it into the
3.183 +@code{src/} directory somewhere, particularly for ease of integrating
3.184 +with the build system.
3.185 +
3.186 +In the case of the error model, it is very related to the packet
3.187 +class, so it makes sense to implement this in the @code{src/common/}
3.188 +directory where ns-3 packets are implemented.
3.189 +
3.190 +@subsection waf and wscript
3.191 +
3.192 +ns-3 uses the @uref{http://www.freehackers.org/~tnagy/waf.html,,Waf}
3.193 +build system. You will want to integrate your new
3.194 +ns-3 uses the Waf build system. You will want to integrate your new
3.195 +source files into this system. This requires that you add your files
3.196 +to the @code{wscript} file found in each directory.
3.197 +
3.198 +Let's start with empty files error-model.h and error-model.cc, and
3.199 +add this to @code{src/common/wscript}. It is really just a matter
3.200 +of adding the .cc file to the rest of the source files, and the .h
3.201 +file to the list of the header files.
3.202 +
3.203 +Now, pop up to the top level directory and type "./waf check". You
3.204 +shouldn't have broken anything by this operation.
3.205 +@subsection include guards
3.206 +Next, let's add some
3.207 +@uref{http://en.wikipedia.org/wiki/Include_guard,,include guards} in our
3.208 +header file.
3.209 +@verbatim
3.210 +#ifndef ERROR_MODEL_H
3.211 +#define ERROR_MODEL_H
3.212 +...
3.213 +#endif
3.214 +@end verbatim
3.215 +
3.216 +@subsection namespace ns3
3.217 +ns-3 uses the ns3 @uref{http://en.wikipedia.org/wiki/Namespace_(computer_science)#Use_in_common_languages,,namespace}
3.218 +to isolate its symbols from other namespaces.
3.219 +Typically, a user will next put an ns-3 namespace block in both the cc and
3.220 +h file.
3.221 +@verbatim
3.222 +namespace ns3 {
3.223 +...
3.224 +}
3.225 +@end verbatim
3.226 +
3.227 +At this point, we have some skeletal files in which we can start defining
3.228 +our new classes. The header file looks like this:
3.229 +
3.230 +@verbatim
3.231 +#ifndef ERROR_MODEL_H
3.232 +#define ERROR_MODEL_H
3.233 +
3.234 +namespace ns3 {
3.235 +
3.236 +} // namespace ns3
3.237 +#endif
3.238 +@end verbatim
3.239 +while the @code{error-model.cc} file simply looks like this:
3.240 +@verbatim
3.241 +#include "error-model.h"
3.242 +
3.243 +namespace ns3 {
3.244 +
3.245 +} // namespace ns3
3.246 +@end verbatim
3.247 +These files should compile since they don't really have any contents.
3.248 +We're now ready to start adding classes.
3.249 +
3.250 +@node Initial Implementation
3.251 +@section Initial Implementation
3.252 +
3.253 +At this point, we're still working on some scaffolding, but we can
3.254 +begin to define our classes, with the functionality to be added later.
3.255 +
3.256 +@subsection use of class Object?
3.257 +
3.258 +This is an important design step; whether to use @code{class Object} as
3.259 +a base class for your new classes.
3.260 +
3.261 +As described in the chapter on the ns-3 @ref{Object model}, classes that
3.262 +inherit from @code{class Object} get special properties:
3.263 +@itemize @bullet
3.264 +@item the ns-3 type and attribute system (see @ref{Attributes})
3.265 +@item an object aggregation system
3.266 +@item a smart-pointer reference counting system (class Ptr)
3.267 +@end itemize
3.268 +
3.269 +Classes that derive from @code{class ObjectBase} get the first two
3.270 +properties above, but do not get smart pointers. Classes that
3.271 +derive from @code{class RefCountBase} get only the smart-pointer
3.272 +reference counting system.
3.273 +
3.274 +In practice, @code{class Object} is the variant of the three above that
3.275 +the ns-3 developer will most commonly encounter.
3.276 +
3.277 +In our case, we want to make use of the attribute system, and we
3.278 +will be passing instances of this object across the ns-3 public API,
3.279 +so @code{class Object} is appropriate for us.
3.280 +
3.281 +@subsection initial classes
3.282 +
3.283 +One way to proceed is to start by defining the bare minimum functions
3.284 +and see if they will compile. Let's review what all is needed to
3.285 +implement when we derive from class Object.
3.286 +
3.287 +@verbatim
3.288 +#ifndef ERROR_MODEL_H
3.289 +#define ERROR_MODEL_H
3.290 +
3.291 +#include "ns3/object.h"
3.292 +
3.293 +namespace ns3 {
3.294 +
3.295 +class ErrorModel : public Object
3.296 +{
3.297 +public:
3.298 + static TypeId GetTypeId (void);
3.299 +
3.300 + ErrorModel ();
3.301 + virtual ~ErrorModel ();
3.302 +};
3.303 +
3.304 +class RateErrorModel : public ErrorModel
3.305 +{
3.306 +public:
3.307 + static TypeId GetTypeId (void);
3.308 +
3.309 + RateErrorModel ();
3.310 + virtual ~RateErrorModel ();
3.311 +};
3.312 +#endif
3.313 +@end verbatim
3.314 +
3.315 +A few things to note here. We need to include @code{object.h}. The
3.316 +convention in ns-3 is that if the header file is co-located in the
3.317 +same directory, it may be included without any path prefix. Therefore,
3.318 +if we were implementing ErrorModel in @code{src/core} directory, we could
3.319 +have just said "@code{#include "object.h"}". But we are in @code{src/common},
3.320 +so we must include it as "@code{#include "ns3/object.h"}". Note also
3.321 +that this goes outside the namespace declaration.
3.322 +
3.323 +Second, each class must implement a static public member function
3.324 +called @code{GetTypeId (void)}.
3.325 +
3.326 +Third, it is a good idea to implement constructors and destructors rather
3.327 +than to let the compiler generate them, and to make the destructor virtual.
3.328 +In C++, note also that copy assignment operator and copy constructors
3.329 +are auto-generated if they are not defined, so if you do not want those,
3.330 +you should implement those as private members. This aspect of C++ is
3.331 +discussed in Scott Meyers' Effective C++ book. item 45.
3.332 +
3.333 +Let's now look at some corresponding skeletal implementation code in the .cc
3.334 +file.
3.335 +
3.336 +@verbatim
3.337 +#include "error-model.h"
3.338 +
3.339 +namespace ns3 {
3.340 +
3.341 +NS_OBJECT_ENSURE_REGISTERED (ErrorModel);
3.342 +
3.343 +TypeId ErrorModel::GetTypeId (void)
3.344 +{
3.345 + static TypeId tid = TypeId ("ns3::ErrorModel")
3.346 + .SetParent<Object> ()
3.347 + ;
3.348 + return tid;
3.349 +}
3.350 +
3.351 +ErrorModel::ErrorModel ()
3.352 +{
3.353 +}
3.354 +
3.355 +ErrorModel::~ErrorModel ()
3.356 +{
3.357 +}
3.358 +
3.359 +NS_OBJECT_ENSURE_REGISTERED (RateErrorModel);
3.360 +
3.361 +TypeId RateErrorModel::GetTypeId (void)
3.362 +{
3.363 + static TypeId tid = TypeId ("ns3::RateErrorModel")
3.364 + .SetParent<ErrorModel> ()
3.365 + .AddConstructor<RateErrorModel> ()
3.366 + ;
3.367 + return tid;
3.368 +}
3.369 +
3.370 +RateErrorModel::RateErrorModel ()
3.371 +{
3.372 +}
3.373 +
3.374 +RateErrorModel::~RateErrorModel ()
3.375 +{
3.376 +}
3.377 +@end verbatim
3.378 +
3.379 +What is the @code{GetTypeId (void)} function? This function does a few
3.380 +things. It registers a unique string into the TypeId system. It establishes
3.381 +the hierarchy of objects in the attribute system (via @code{SetParent}).
3.382 +It also declares that certain objects can be created via the object
3.383 +creation framework (@code{AddConstructor}).
3.384 +
3.385 +The macro @code{NS_OBJECT_ENSURE_REGISTERED (classname)} is needed also
3.386 +once for every class that defines a new GetTypeId method, and it does
3.387 +the actual registration of the class into the system.
3.388 +The @ref{Object model} chapter discusses this in more detail.
3.389 +
3.390 +@subsection how to include files from elsewhere
3.391 +@subsection log component
3.392 +
3.393 +Here, write a bit about adding ns-3 logging macros. Note that
3.394 +LOG_COMPONENT_DEFINE is done outside the namespace ns3
3.395 +
3.396 +@subsection constructor, empty function prototypes
3.397 +
3.398 +@subsection key variables (default values, attributes)
3.399 +
3.400 +@subsection test program 1
3.401 +
3.402 +
3.403 +
3.404 +@subsection Object Framework
3.405 +
3.406 + static const ClassId cid;
3.407 +
3.408 +
3.409 +const InterfaceId ErrorModel::iid =
3.410 + MakeInterfaceId ("ErrorModel", Object::iid);
3.411 +
3.412 +const ClassId ErrorModel::cid =
3.413 + MakeClassId<ErrorModel> ("ErrorModel", ErrorModel::iid);
3.414 +@end verbatim
3.415 +
3.416 +
3.417 +@node Adding-a-sample-script
3.418 +@section Adding a sample script
3.419 +
3.420 +At this point, one may want to try to take the basic scaffolding
3.421 +defined above and add it into the system. Performing this step
3.422 +now allows one to use a simpler model when plumbing into the system
3.423 +and may also reveal whether any design or API modifications need to be
3.424 +made. Once this is done, we will return to building out the functionality
3.425 +of the ErrorModels themselves.
3.426 +
3.427 +@subsection Add basic support in the class
3.428 +
3.429 +@verbatim
3.430 +point-to-point-net-device.h
3.431 + class ErrorModel;
3.432 +
3.433 + /**
3.434 + * Error model for receive packet events
3.435 + */
3.436 + Ptr<ErrorModel> m_receiveErrorModel;
3.437 +
3.438 +@end verbatim
3.439 +
3.440 +@subsection Add Accessor
3.441 +
3.442 +@verbatim
3.443 +
3.444 + void
3.445 +PointToPointNetDevice::SetReceiveErrorModel (Ptr<ErrorModel> em)
3.446 +{
3.447 + NS_LOG_FUNCTION (this << em);
3.448 + m_receiveErrorModel = em;
3.449 +}
3.450 +
3.451 + .AddAttribute ("ReceiveErrorModel",
3.452 + "The receiver error model used to simulate packet loss",
3.453 + PointerValue (),
3.454 + MakePointerAccessor (&PointToPointNetDevice::m_receiveErrorModel),
3.455 + MakePointerChecker<ErrorModel> ())
3.456 +@end verbatim
3.457 +
3.458 +@subsection Plumb into the system
3.459 +
3.460 +@verbatim
3.461 +void PointToPointNetDevice::Receive (Ptr<Packet> packet)
3.462 +{
3.463 + NS_LOG_FUNCTION (this << packet);
3.464 + uint16_t protocol = 0;
3.465 +
3.466 + if (m_receiveErrorModel && m_receiveErrorModel->IsCorrupt (packet) )
3.467 + {
3.468 +//
3.469 +// If we have an error model and it indicates that it is time to lose a
3.470 +// corrupted packet, don't forward this packet up, let it go.
3.471 +//
3.472 + m_dropTrace (packet);
3.473 + }
3.474 + else
3.475 + {
3.476 +//
3.477 +// Hit the receive trace hook, strip off the point-to-point protocol header
3.478 +// and forward this packet up the protocol stack.
3.479 +//
3.480 + m_rxTrace (packet);
3.481 + ProcessHeader(packet, protocol);
3.482 + m_rxCallback (this, packet, protocol, GetRemote ());
3.483 + if (!m_promiscCallback.IsNull ())
3.484 + { m_promiscCallback (this, packet, protocol, GetRemote (), GetAddress (), NetDevice::PACKET_HOST);
3.485 + }
3.486 + }
3.487 +}
3.488 +@end verbatim
3.489 +
3.490 +@subsection Create null functional script
3.491 +
3.492 +@verbatim
3.493 +simple-error-model.cc
3.494 +
3.495 + // Error model
3.496 + // We want to add an error model to node 3's NetDevice
3.497 + // We can obtain a handle to the NetDevice via the channel and node
3.498 + // pointers
3.499 + Ptr<PointToPointNetDevice> nd3 = PointToPointTopology::GetNetDevice
3.500 + (n3, channel2);
3.501 + Ptr<ErrorModel> em = Create<ErrorModel> ();
3.502 + nd3->SetReceiveErrorModel (em);
3.503 +
3.504 +
3.505 +bool
3.506 +ErrorModel::DoCorrupt (Packet& p)
3.507 +{
3.508 + NS_LOG_FUNCTION;
3.509 + NS_LOG_UNCOND("Corrupt!");
3.510 + return false;
3.511 +}
3.512 +@end verbatim
3.513 +
3.514 +At this point, we can run the program with our trivial ErrorModel
3.515 +plumbed into the receive path of the PointToPointNetDevice. It
3.516 +prints out the string "Corrupt!" for each packet received at
3.517 +node n3. Next, we return to the error model to add in a subclass
3.518 +that performs more interesting error modeling.
3.519 +
3.520 +@section Add subclass
3.521 +
3.522 +The trivial base class ErrorModel does not do anything interesting,
3.523 +but it provides a useful base class interface (Corrupt () and Reset ()),
3.524 +forwarded to virtual functions that can be subclassed. Let's next
3.525 +consider what we call a BasicErrorModel which is based on the
3.526 +ns-2 ErrorModel class (in ns-2/queue/errmodel.@{cc,h@}).
3.527 +
3.528 +What properties do we want this to have, from a user interface
3.529 +perspective? We would like for the user to be able to trivially
3.530 +swap out the type of ErrorModel used in the NetDevice. We would also
3.531 +like the capability to set configurable parameters.
3.532 +
3.533 +Here are a few simple requirements we will consider:
3.534 +@itemize @bullet
3.535 +@item Ability to set the random variable that governs the losses
3.536 +(default is UniformVariable)
3.537 +@item Ability to set the unit (bit, byte, packet, time) of granularity
3.538 +over which errors are applied.
3.539 +@item Ability to set the rate of errors (e.g. 10^-3) corresponding to
3.540 +the above unit of granularity.
3.541 +@item Ability to enable/disable (default is enabled)
3.542 +@end itemize
3.543 +
3.544 +@subsection How to subclass
3.545 +
3.546 +We declare BasicErrorModel to be a subclass of ErrorModel as follows,
3.547 +
3.548 +@verbatim
3.549 +class BasicErrorModel : public ErrorModel
3.550 +{
3.551 +public:
3.552 + static TypeId GetTypeId (void);
3.553 + ...
3.554 +private:
3.555 + // Implement base class pure virtual functions
3.556 + virtual bool DoCorrupt (Ptr<Packet> p);
3.557 + virtual bool DoReset (void);
3.558 + ...
3.559 +}
3.560 +@end verbatim
3.561 +
3.562 +and configure the subclass GetTypeId function by setting a unique
3.563 +TypeId string and setting the Parent to ErrorModel:
3.564 +
3.565 +@verbatim
3.566 +TypeId RateErrorModel::GetTypeId (void)
3.567 +{
3.568 + static TypeId tid = TypeId ("ns3::RateErrorModel")
3.569 + .SetParent<ErrorModel> ()
3.570 + .AddConstructor<RateErrorModel> ()
3.571 + ...
3.572 +@end verbatim
3.573 +
3.574 +@node Build-core-functions-and-unit-tests
3.575 +@section Build core functions and unit tests
3.576 +
3.577 +@subsection assert macros
3.578 +
3.579 +@subsection Writing unit tests
3.580 +
3.581 +