net_mem.c
Go to the documentation of this file.
1 /**
2  * @file net_mem.c
3  * @brief Memory management
4  *
5  * @section License
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2010-2023 Oryx Embedded SARL. All rights reserved.
10  *
11  * This file is part of CycloneTCP Open.
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26  *
27  * @author Oryx Embedded SARL (www.oryx-embedded.com)
28  * @version 2.2.4
29  **/
30 
31 //Switch to the appropriate trace level
32 #define TRACE_LEVEL MEM_TRACE_LEVEL
33 
34 //Dependencies
35 #include "core/net.h"
36 #include "core/net_mem.h"
37 #include "debug.h"
38 
39 //Maximum number of chunks for dynamically allocated buffers
40 #if (IPV4_SUPPORT == ENABLED && IPV6_SUPPORT == ENABLED)
41  #define MAX_CHUNK_COUNT (N(MAX(IPV4_MAX_FRAG_DATAGRAM_SIZE, IPV6_MAX_FRAG_DATAGRAM_SIZE)) + 3)
42 #elif (IPV4_SUPPORT == ENABLED)
43  #define MAX_CHUNK_COUNT (N(IPV4_MAX_FRAG_DATAGRAM_SIZE) + 3)
44 #elif (IPV6_SUPPORT == ENABLED)
45  #define MAX_CHUNK_COUNT (N(IPV6_MAX_FRAG_DATAGRAM_SIZE) + 3)
46 #endif
47 
48 //Use fixed-size blocks allocation?
49 #if (NET_MEM_POOL_SUPPORT == ENABLED)
50 
51 //Mutex preventing simultaneous access to the memory pool
52 static OsMutex memPoolMutex;
53 //Memory pool
54 static uint32_t memPool[NET_MEM_POOL_BUFFER_COUNT][NET_MEM_POOL_BUFFER_SIZE / 4];
55 //Allocation table
56 static bool_t memPoolAllocTable[NET_MEM_POOL_BUFFER_COUNT];
57 //Number of buffers currently allocated
59 //Maximum number of buffers that have been allocated so far
61 
62 #endif
63 
64 
65 /**
66  * @brief Memory pool initialization
67  * @return Error code
68  **/
69 
71 {
72 //Use fixed-size blocks allocation?
73 #if (NET_MEM_POOL_SUPPORT == ENABLED)
74  //Create a mutex to prevent simultaneous access to the memory pool
75  if(!osCreateMutex(&memPoolMutex))
76  {
77  //Failed to create mutex
79  }
80 
81  //Clear allocation table
82  osMemset(memPoolAllocTable, 0, sizeof(memPoolAllocTable));
83 
84  //Clear statistics
86  memPoolMaxUsage = 0;
87 #endif
88 
89  //Successful initialization
90  return NO_ERROR;
91 }
92 
93 
94 /**
95  * @brief Allocate a memory block
96  * @param[in] size Bytes to allocate
97  * @return Pointer to the allocated space or NULL if there is insufficient memory available
98  **/
99 
100 void *memPoolAlloc(size_t size)
101 {
102 #if (NET_MEM_POOL_SUPPORT == ENABLED)
103  uint_t i;
104 #endif
105 
106  //Pointer to the allocated memory block
107  void *p = NULL;
108 
109  //Debug message
110  TRACE_DEBUG("Allocating %" PRIuSIZE " bytes...\r\n", size);
111 
112 //Use fixed-size blocks allocation?
113 #if (NET_MEM_POOL_SUPPORT == ENABLED)
114  //Acquire exclusive access to the memory pool
115  osAcquireMutex(&memPoolMutex);
116 
117  //Enforce block size
118  if(size <= NET_MEM_POOL_BUFFER_SIZE)
119  {
120  //Loop through allocation table
121  for(i = 0; i < NET_MEM_POOL_BUFFER_COUNT; i++)
122  {
123  //Check whether the current block is free
124  if(!memPoolAllocTable[i])
125  {
126  //Mark the current entry as used
127  memPoolAllocTable[i] = TRUE;
128  //Point to the corresponding memory block
129  p = memPool[i];
130 
131  //Update statistics
133  //Maximum number of buffers that have been allocated so far
135 
136  //Exit immediately
137  break;
138  }
139  }
140  }
141 
142  //Release exclusive access to the memory pool
143  osReleaseMutex(&memPoolMutex);
144 #else
145  //Allocate a memory block
146  p = osAllocMem(size);
147 #endif
148 
149  //Failed to allocate memory?
150  if(!p)
151  {
152  //Debug message
153  TRACE_WARNING("Memory allocation failed!\r\n");
154  }
155 
156  //Return a pointer to the allocated memory block
157  return p;
158 }
159 
160 
161 /**
162  * @brief Release a memory block
163  * @param[in] p Previously allocated memory block to be freed
164  **/
165 
166 void memPoolFree(void *p)
167 {
168 //Use fixed-size blocks allocation?
169 #if (NET_MEM_POOL_SUPPORT == ENABLED)
170  uint_t i;
171 
172  //Acquire exclusive access to the memory pool
173  osAcquireMutex(&memPoolMutex);
174 
175  //Loop through allocation table
176  for(i = 0; i < NET_MEM_POOL_BUFFER_COUNT; i++)
177  {
178  if(memPool[i] == p)
179  {
180  //Mark the current block as free
181  memPoolAllocTable[i] = FALSE;
182 
183  //Update statistics
185 
186  //Exit immediately
187  break;
188  }
189  }
190 
191  //Release exclusive access to the memory pool
192  osReleaseMutex(&memPoolMutex);
193 #else
194  //Release memory block
195  osFreeMem(p);
196 #endif
197 }
198 
199 
200 /**
201  * @brief Get memory pool usage
202  * @param[out] currentUsage Number of buffers currently allocated
203  * @param[out] maxUsage Maximum number of buffers that have been allocated so far
204  * @param[out] size Total number of buffers in the memory pool
205  **/
206 
207 void memPoolGetStats(uint_t *currentUsage, uint_t *maxUsage, uint_t *size)
208 {
209 //Use fixed-size blocks allocation?
210 #if (NET_MEM_POOL_SUPPORT == ENABLED)
211  //Number of buffers currently allocated
212  if(currentUsage != NULL)
213  *currentUsage = memPoolCurrentUsage;
214 
215  //Maximum number of buffers that have been allocated so far
216  if(maxUsage != NULL)
217  *maxUsage = memPoolMaxUsage;
218 
219  //Total number of buffers in the memory pool
220  if(size != NULL)
222 #else
223  //Memory pool is not used...
224  if(currentUsage != NULL)
225  *currentUsage = 0;
226 
227  if(maxUsage != NULL)
228  *maxUsage = 0;
229 
230  if(size != NULL)
231  *size = 0;
232 #endif
233 }
234 
235 
236 /**
237  * @brief Allocate a multi-part buffer
238  * @param[in] length Desired length
239  * @return Pointer to the allocated buffer or NULL if there is
240  * insufficient memory available
241  **/
242 
244 {
245  error_t error;
246  NetBuffer *buffer;
247 
248  //Allocate memory to hold the multi-part buffer
250  //Failed to allocate memory?
251  if(buffer == NULL)
252  return NULL;
253 
254  //The multi-part buffer consists of a single chunk
255  buffer->chunkCount = 1;
256  buffer->maxChunkCount = MAX_CHUNK_COUNT;
257  buffer->chunk[0].address = (uint8_t *) buffer + CHUNKED_BUFFER_HEADER_SIZE;
259  buffer->chunk[0].size = 0;
260 
261  //Adjust the length of the buffer
262  error = netBufferSetLength(buffer, length);
263  //Any error to report?
264  if(error)
265  {
266  //Clean up side effects
267  netBufferFree(buffer);
268  //Report an failure
269  return NULL;
270  }
271 
272  //Successful memory allocation
273  return buffer;
274 }
275 
276 
277 /**
278  * @brief Dispose a multi-part buffer
279  * @param[in] buffer Pointer to the multi-part buffer to be released
280  **/
281 
283 {
284  //Properly dispose data chunks
285  netBufferSetLength(buffer, 0);
286  //Release multi-part buffer
287  memPoolFree(buffer);
288 }
289 
290 
291 /**
292  * @brief Get the actual length of a multi-part buffer
293  * @param[in] buffer Pointer to a multi-part buffer
294  * @return Actual length in bytes
295  **/
296 
297 size_t netBufferGetLength(const NetBuffer *buffer)
298 {
299  uint_t i;
300 
301  //Total length
302  size_t length = 0;
303 
304  //Loop through data chunks
305  for(i = 0; i < buffer->chunkCount; i++)
306  length += buffer->chunk[i].length;
307 
308  //Return total length
309  return length;
310 }
311 
312 
313 /**
314  * @brief Adjust the length of a multi-part buffer
315  * @param[in] buffer Pointer to the multi-part buffer whose length is to be changed
316  * @param[in] length Desired length
317  * @return Error code
318  **/
319 
321 {
322  uint_t i;
323  uint_t chunkCount;
324  ChunkDesc *chunk;
325 
326  //Get the actual number of chunks
327  chunkCount = buffer->chunkCount;
328 
329  //Loop through data chunks
330  for(i = 0; i < chunkCount && length > 0; i++)
331  {
332  //Point to the chunk descriptor;
333  chunk = &buffer->chunk[i];
334 
335  //Adjust the length of the current chunk when possible
336  if(length <= chunk->length)
337  {
338  chunk->length = length;
339  }
340  else if(chunk->size > 0 && i == (chunkCount - 1))
341  {
342  chunk->length = MIN(length, chunk->size);
343  }
344 
345  //Prepare to process next chunk
346  length -= chunk->length;
347  }
348 
349  //The size of the buffer should be decreased?
350  if(!length)
351  {
352  //Adjust the number of chunks
353  buffer->chunkCount = i;
354 
355  //Delete unnecessary data chunks
356  while(i < chunkCount)
357  {
358  //Point to the chunk descriptor;
359  chunk = &buffer->chunk[i];
360 
361  //Release previously allocated memory
362  if(chunk->size > 0)
363  memPoolFree(chunk->address);
364 
365  //Mark the current chunk as free
366  chunk->address = NULL;
367  chunk->length = 0;
368  chunk->size = 0;
369 
370  //Next chunk
371  i++;
372  }
373  }
374  //The size of the buffer should be increased?
375  else
376  {
377  //Add as many chunks as necessary
378  while(i < buffer->maxChunkCount && length > 0)
379  {
380  //Point to the chunk descriptor;
381  chunk = &buffer->chunk[i];
382 
383  //Allocate memory to hold a new chunk
385  //Failed to allocate memory?
386  if(!chunk->address)
387  return ERROR_OUT_OF_MEMORY;
388 
389  //Allocated memory
391  //Actual length of the data chunk
393 
394  //Prepare to process next chunk
395  length -= chunk->length;
396  buffer->chunkCount++;
397  i++;
398  }
399  }
400 
401  //Return status code
402  return (length > 0) ? ERROR_OUT_OF_RESOURCES : NO_ERROR;
403 }
404 
405 
406 /**
407  * @brief Returns a pointer to the data at the specified position
408  * @param[in] buffer Pointer to a multi-part buffer
409  * @param[in] offset Offset from the beginning of the buffer
410  * @return Pointer the data at the specified position
411  **/
412 
413 void *netBufferAt(const NetBuffer *buffer, size_t offset)
414 {
415  uint_t i;
416 
417  //Loop through data chunks
418  for(i = 0; i < buffer->chunkCount; i++)
419  {
420  //The data at the specified offset resides in the current chunk?
421  if(offset < buffer->chunk[i].length)
422  return (uint8_t *) buffer->chunk[i].address + offset;
423 
424  //Jump to the next chunk
425  offset -= buffer->chunk[i].length;
426  }
427 
428  //Invalid offset...
429  return NULL;
430 }
431 
432 
433 /**
434  * @brief Concatenate two multi-part buffers
435  * @param[out] dest Pointer to the destination buffer
436  * @param[in] src Pointer to the source buffer
437  * @param[in] srcOffset Read offset
438  * @param[in] length Number of bytes to read from the source buffer
439  * @return Error code
440  **/
441 
443  const NetBuffer *src, size_t srcOffset, size_t length)
444 {
445  uint_t i;
446  uint_t j;
447 
448  //Skip the beginning of the source data
449  for(j = 0; j < src->chunkCount; j++)
450  {
451  //The data at the specified offset resides in the current chunk?
452  if(srcOffset < src->chunk[j].length)
453  break;
454 
455  //Jump to the next chunk
456  srcOffset -= src->chunk[j].length;
457  }
458 
459  //Invalid offset?
460  if(j >= src->chunkCount)
462 
463  //Position to the end of the destination data
464  i = dest->chunkCount;
465 
466  //Copy data blocks
467  while(length > 0 && i < dest->maxChunkCount && j < src->chunkCount)
468  {
469  //Copy current block
470  dest->chunk[i].address = (uint8_t *) src->chunk[j].address + srcOffset;
471  dest->chunk[i].length = src->chunk[j].length - srcOffset;
472  dest->chunk[i].size = 0;
473 
474  //Limit the number of bytes to copy
475  if(length < dest->chunk[i].length)
476  dest->chunk[i].length = length;
477 
478  //Decrement the number of remaining bytes
479  length -= dest->chunk[i].length;
480  //Increment the number of chunks
481  dest->chunkCount++;
482 
483  //Adjust variables
484  srcOffset = 0;
485  i++;
486  j++;
487  }
488 
489  //Return status code
490  return (length > 0) ? ERROR_FAILURE : NO_ERROR;
491 }
492 
493 
494 /**
495  * @brief Copy data between multi-part buffers
496  * @param[out] dest Pointer to the destination buffer
497  * @param[in] destOffset Write offset
498  * @param[in] src Pointer to the source buffer
499  * @param[in] srcOffset Read offset
500  * @param[in] length Number of bytes to be copied
501  * @return Error code
502  **/
503 
504 error_t netBufferCopy(NetBuffer *dest, size_t destOffset,
505  const NetBuffer *src, size_t srcOffset, size_t length)
506 {
507  uint_t i;
508  uint_t j;
509  uint_t n;
510  uint8_t *p;
511  uint8_t *q;
512 
513  //Skip the beginning of the source data
514  for(i = 0; i < dest->chunkCount; i++)
515  {
516  //The data at the specified offset resides in the current chunk?
517  if(destOffset < dest->chunk[i].length)
518  break;
519 
520  //Jump to the next chunk
521  destOffset -= dest->chunk[i].length;
522  }
523 
524  //Invalid offset?
525  if(i >= dest->chunkCount)
527 
528  //Skip the beginning of the source data
529  for(j = 0; j < src->chunkCount; j++)
530  {
531  //The data at the specified offset resides in the current chunk?
532  if(srcOffset < src->chunk[j].length)
533  break;
534 
535  //Jump to the next chunk
536  srcOffset -= src->chunk[j].length;
537  }
538 
539  //Invalid offset?
540  if(j >= src->chunkCount)
542 
543  while(length > 0 && i < dest->chunkCount && j < src->chunkCount)
544  {
545  //Point to the first data byte
546  p = (uint8_t *) dest->chunk[i].address + destOffset;
547  q = (uint8_t *) src->chunk[j].address + srcOffset;
548 
549  //Compute the number of bytes to copy
550  n = MIN(length, dest->chunk[i].length - destOffset);
551  n = MIN(n, src->chunk[j].length - srcOffset);
552 
553  //Copy data
554  osMemcpy(p, q, n);
555 
556  destOffset += n;
557  srcOffset += n;
558  length -= n;
559 
560  if(destOffset >= dest->chunk[i].length)
561  {
562  destOffset = 0;
563  i++;
564  }
565 
566  if(srcOffset >= src->chunk[j].length)
567  {
568  srcOffset = 0;
569  j++;
570  }
571  }
572 
573  //Return status code
574  return (length > 0) ? ERROR_FAILURE : NO_ERROR;
575 }
576 
577 
578 /**
579  * @brief Append data a multi-part buffer
580  * @param[out] dest Pointer to a multi-part buffer
581  * @param[in] src User buffer containing the data to be appended
582  * @param[in] length Number of bytes in the user buffer
583  * @return Error code
584  **/
585 
586 error_t netBufferAppend(NetBuffer *dest, const void *src, size_t length)
587 {
588  uint_t i;
589 
590  //Make sure there is enough space to add an extra chunk
591  if(dest->chunkCount >= dest->maxChunkCount)
592  return ERROR_FAILURE;
593 
594  //Position to the end of the buffer
595  i = dest->chunkCount;
596 
597  //Insert a new chunk at the end of the list
598  dest->chunk[i].address = (void *) src;
599  dest->chunk[i].length = length;
600  dest->chunk[i].size = 0;
601 
602  //Increment the number of chunks
603  dest->chunkCount++;
604 
605  //Successful processing
606  return NO_ERROR;
607 }
608 
609 
610 /**
611  * @brief Write data to a multi-part buffer
612  * @param[out] dest Pointer to a multi-part buffer
613  * @param[in] destOffset Offset from the beginning of the multi-part buffer
614  * @param[in] src User buffer containing the data to be written
615  * @param[in] length Number of bytes to copy
616  * @return Actual number of bytes copied
617  **/
618 
620  size_t destOffset, const void *src, size_t length)
621 {
622  uint_t i;
623  uint_t n;
624  size_t totalLength;
625  uint8_t *p;
626 
627  //Total number of bytes written
628  totalLength = 0;
629 
630  //Loop through data chunks
631  for(i = 0; i < dest->chunkCount && totalLength < length; i++)
632  {
633  //Is there any data to copy in the current chunk?
634  if(destOffset < dest->chunk[i].length)
635  {
636  //Point to the first byte to be written
637  p = (uint8_t *) dest->chunk[i].address + destOffset;
638  //Compute the number of bytes to copy at a time
639  n = MIN(length - totalLength, dest->chunk[i].length - destOffset);
640 
641  //Copy data
642  osMemcpy(p, src, n);
643 
644  //Advance read pointer
645  src = (uint8_t *) src + n;
646  //Total number of bytes written
647  totalLength += n;
648  //Process the next block from the start
649  destOffset = 0;
650  }
651  else
652  {
653  //Skip the current chunk
654  destOffset -= dest->chunk[i].length;
655  }
656  }
657 
658  //Return the actual number of bytes written
659  return totalLength;
660 }
661 
662 
663 /**
664  * @brief Read data from a multi-part buffer
665  * @param[out] dest Pointer to the buffer where to return the data
666  * @param[in] src Pointer to a multi-part buffer
667  * @param[in] srcOffset Offset from the beginning of the multi-part buffer
668  * @param[in] length Number of bytes to copy
669  * @return Actual number of bytes copied
670  **/
671 
672 size_t netBufferRead(void *dest, const NetBuffer *src,
673  size_t srcOffset, size_t length)
674 {
675  uint_t i;
676  uint_t n;
677  size_t totalLength;
678  uint8_t *p;
679 
680  //Total number of bytes copied
681  totalLength = 0;
682 
683  //Loop through data chunks
684  for(i = 0; i < src->chunkCount && totalLength < length; i++)
685  {
686  //Is there any data to copy from the current chunk?
687  if(srcOffset < src->chunk[i].length)
688  {
689  //Point to the first byte to be read
690  p = (uint8_t *) src->chunk[i].address + srcOffset;
691  //Compute the number of bytes to copy at a time
692  n = MIN(length - totalLength, src->chunk[i].length - srcOffset);
693 
694  //Copy data
695  osMemcpy(dest, p, n);
696 
697  //Advance write pointer
698  dest = (uint8_t *) dest + n;
699  //Total number of bytes copied
700  totalLength += n;
701  //Process the next block from the start
702  srcOffset = 0;
703  }
704  else
705  {
706  //Skip the current chunk
707  srcOffset -= src->chunk[i].length;
708  }
709  }
710 
711  //Return the actual number of bytes copied
712  return totalLength;
713 }
uint8_t length
Definition: coap_common.h:193
void memPoolGetStats(uint_t *currentUsage, uint_t *maxUsage, uint_t *size)
Get memory pool usage.
Definition: net_mem.c:207
int bool_t
Definition: compiler_port.h:53
bool_t osCreateMutex(OsMutex *mutex)
Create a mutex object.
void memPoolFree(void *p)
Release a memory block.
Definition: net_mem.c:166
size_t netBufferRead(void *dest, const NetBuffer *src, size_t srcOffset, size_t length)
Read data from a multi-part buffer.
Definition: net_mem.c:672
uint8_t p
Definition: ndp.h:298
Memory management.
Structure describing a buffer that spans multiple chunks.
Definition: net_mem.h:89
uint_t chunkCount
Definition: net_mem.h:90
#define TRUE
Definition: os_port.h:52
@ ERROR_OUT_OF_RESOURCES
Definition: error.h:64
@ ERROR_OUT_OF_MEMORY
Definition: error.h:63
void * memPoolAlloc(size_t size)
Allocate a memory block.
Definition: net_mem.c:100
error_t netBufferConcat(NetBuffer *dest, const NetBuffer *src, size_t srcOffset, size_t length)
Concatenate two multi-part buffers.
Definition: net_mem.c:442
__weak_func void * osAllocMem(size_t size)
Allocate a memory block.
#define NET_MEM_POOL_BUFFER_SIZE
Definition: net_mem.h:55
uint16_t length
Definition: net_mem.h:79
#define FALSE
Definition: os_port.h:48
@ ERROR_INVALID_PARAMETER
Invalid parameter.
Definition: error.h:47
#define osMemcpy(dest, src, length)
Definition: os_port.h:140
error_t
Error codes.
Definition: error.h:43
Structure describing a chunk of data.
Definition: net_mem.h:77
__weak_func void osFreeMem(void *p)
Release a previously allocated memory block.
void * netBufferAt(const NetBuffer *buffer, size_t offset)
Returns a pointer to the data at the specified position.
Definition: net_mem.c:413
void * address
Definition: net_mem.h:78
#define CHUNKED_BUFFER_HEADER_SIZE
Definition: net_mem.h:61
@ ERROR_FAILURE
Generic error code.
Definition: error.h:45
error_t memPoolInit(void)
Memory pool initialization.
Definition: net_mem.c:70
void netBufferFree(NetBuffer *buffer)
Dispose a multi-part buffer.
Definition: net_mem.c:282
uint_t maxChunkCount
Definition: net_mem.h:91
error_t netBufferCopy(NetBuffer *dest, size_t destOffset, const NetBuffer *src, size_t srcOffset, size_t length)
Copy data between multi-part buffers.
Definition: net_mem.c:504
size_t netBufferGetLength(const NetBuffer *buffer)
Get the actual length of a multi-part buffer.
Definition: net_mem.c:297
#define MIN(a, b)
Definition: os_port.h:65
Mutex object.
NetBuffer * netBufferAlloc(size_t length)
Allocate a multi-part buffer.
Definition: net_mem.c:243
#define TRACE_WARNING(...)
Definition: debug.h:85
#define TRACE_DEBUG(...)
Definition: debug.h:107
#define MAX(a, b)
Definition: os_port.h:69
#define NET_MEM_POOL_BUFFER_COUNT
Definition: net_mem.h:48
ChunkDesc chunk[]
Definition: net_mem.h:92
uint8_t n
error_t netBufferAppend(NetBuffer *dest, const void *src, size_t length)
Append data a multi-part buffer.
Definition: net_mem.c:586
void osAcquireMutex(OsMutex *mutex)
Acquire ownership of the specified mutex object.
void osReleaseMutex(OsMutex *mutex)
Release ownership of the specified mutex object.
error_t netBufferSetLength(NetBuffer *buffer, size_t length)
Adjust the length of a multi-part buffer.
Definition: net_mem.c:320
uint_t memPoolCurrentUsage
Definition: net_mem.c:58
size_t netBufferWrite(NetBuffer *dest, size_t destOffset, const void *src, size_t length)
Write data to a multi-part buffer.
Definition: net_mem.c:619
uint_t memPoolMaxUsage
Definition: net_mem.c:60
#define PRIuSIZE
unsigned int uint_t
Definition: compiler_port.h:50
#define osMemset(p, value, length)
Definition: os_port.h:134
TCP/IP stack core.
#define MAX_CHUNK_COUNT
Definition: net_mem.c:41
uint16_t size
Definition: net_mem.h:80
@ NO_ERROR
Success.
Definition: error.h:44
Debugging facilities.