moved to folders, updated etc
This commit is contained in:
@@ -1,43 +1,15 @@
|
||||
Circular byte buffer
|
||||
====================
|
||||
Buffers
|
||||
=======
|
||||
|
||||
**(and other buffers)**
|
||||
This is a collection of useful buffer modules for C programs.
|
||||
|
||||
This is a circular buffer implementation, useful mainly for embedded systems (buffer for UART RX/TX queues etc).
|
||||
- **circbuf** - generic circular buffer, queue, stack
|
||||
- **matcher** - pattern matcher for character streams
|
||||
- **meanbuf** - averaging (smoothing) buffer.
|
||||
|
||||
It should be reliable with producent / consumer threads (no race conditions, as no length variable is used).
|
||||
|
||||
A Matching buffer, and Averaging buffer are also included—see their header files for instructions.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
```c
|
||||
#include <stdint.h>
|
||||
#include "circbuf.h"
|
||||
|
||||
circbuf_t cb; // buffer instance
|
||||
|
||||
void main()
|
||||
{
|
||||
char c;
|
||||
cbuf_init(&cb, 32); // init the buffer
|
||||
|
||||
// now it's ready for use!
|
||||
|
||||
cbuf_write(&cb, 'A');
|
||||
|
||||
if (cbuf_read(&cb, &c)) {
|
||||
printf("%c", c);
|
||||
}
|
||||
|
||||
cbuf_deinit(&cb); // free the backing array (in embedded system you don't usually need to, allocate once and keep it)
|
||||
}
|
||||
```
|
||||
|
||||
Most functions return a success flag (true - success), so make sure to check the return values.
|
||||
|
||||
False is returned on buffer overflow / underflow. See the header file for details.
|
||||
*Please see READMEs in the project folders for more details.*
|
||||
|
||||
The buffers were developed for embedded microcontrollers (STM32, AVR),
|
||||
but can be used anywhere, they are fully portable.
|
||||
|
||||
Pull requests to add new buffers or improve the current ones are welcome!
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#include "circbuf.h"
|
||||
|
||||
|
||||
void cbuf_init(circbuf_t *inst, size_t capacity)
|
||||
{
|
||||
inst->buf = malloc(capacity);
|
||||
inst->cap = capacity;
|
||||
|
||||
cbuf_clear(inst);
|
||||
}
|
||||
|
||||
|
||||
void cbuf_deinit(circbuf_t *inst)
|
||||
{
|
||||
if (inst->buf != NULL) {
|
||||
free(inst->buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool cbuf_full(circbuf_t *inst)
|
||||
{
|
||||
return (inst->lr == inst->nw);
|
||||
}
|
||||
|
||||
|
||||
static bool can_read_at(circbuf_t *inst, size_t i)
|
||||
{
|
||||
if (inst->lr < inst->nw) {
|
||||
return i > inst->lr && i < inst->nw;
|
||||
} else {
|
||||
// NW < LR
|
||||
return (i < inst->cap && i > inst->lr) || i < inst->nw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool cbuf_empty(circbuf_t *inst)
|
||||
{
|
||||
return ((inst->lr + 1) % inst->cap) == inst->nw;
|
||||
}
|
||||
|
||||
|
||||
bool cbuf_write(circbuf_t *inst, uint8_t b)
|
||||
{
|
||||
if (cbuf_full(inst)) return false;
|
||||
|
||||
inst->buf[inst->nw] = b;
|
||||
|
||||
// increment
|
||||
inst->nw++;
|
||||
if (inst->nw == inst->cap) inst->nw = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool cbuf_read(circbuf_t *inst, uint8_t *b)
|
||||
{
|
||||
if (cbuf_empty(inst)) return false;
|
||||
|
||||
// increment
|
||||
inst->lr++;
|
||||
if (inst->lr == inst->cap) inst->lr = 0;
|
||||
|
||||
*b = inst->buf[inst->lr];
|
||||
|
||||
//zero out the read byte (for debug)
|
||||
inst->buf[inst->lr] = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool cbuf_peek(circbuf_t *inst, size_t nth, uint8_t *b)
|
||||
{
|
||||
// check if can read there (can't use modulo, could "wrap")
|
||||
size_t n = inst->lr;
|
||||
for (size_t i = 0; i <= nth; i++) {
|
||||
// increment N
|
||||
n++;
|
||||
if (n == inst->cap) n = 0;
|
||||
|
||||
if (!can_read_at(inst, n)) return false;
|
||||
}
|
||||
|
||||
*b = inst->buf[n];
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void cbuf_clear(circbuf_t *inst)
|
||||
{
|
||||
inst->lr = inst->cap - 1;
|
||||
inst->nw = 0;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Circular Character Buffer implementation
|
||||
|
||||
typedef struct CircularBuffer_struct {
|
||||
uint8_t *buf;
|
||||
size_t cap;
|
||||
size_t lr; // last read pos
|
||||
size_t nw; // next write pos
|
||||
} circbuf_t;
|
||||
|
||||
|
||||
/** Init a buffer */
|
||||
void cbuf_init(circbuf_t *inst, size_t capacity);
|
||||
|
||||
|
||||
/** Deinit a buffer (free the memory) */
|
||||
void cbuf_deinit(circbuf_t *inst);
|
||||
|
||||
|
||||
/** Test for full buffer */
|
||||
bool cbuf_full(circbuf_t *inst);
|
||||
|
||||
|
||||
/** Test for empty buffer */
|
||||
bool cbuf_empty(circbuf_t *inst);
|
||||
|
||||
|
||||
/** Write a byte to buffer, returns success */
|
||||
bool cbuf_write(circbuf_t *inst, uint8_t b);
|
||||
|
||||
|
||||
/**
|
||||
* Read a byte from the buffer, return susccess.
|
||||
* If `b` is NULL, the read byte is discarded.
|
||||
*/
|
||||
bool cbuf_read(circbuf_t *inst, uint8_t *b);
|
||||
|
||||
|
||||
/**
|
||||
* Get n-th byte from the buffer, without removing it.
|
||||
* Returns false if offset is invalid (ie. empty buffer).
|
||||
*/
|
||||
bool cbuf_peek(circbuf_t *inst, size_t nth, uint8_t *b);
|
||||
|
||||
|
||||
/** Remove all data from buffer */
|
||||
void cbuf_clear(circbuf_t *inst);
|
||||
@@ -0,0 +1,51 @@
|
||||
Circular buffer
|
||||
===============
|
||||
|
||||
This is a C implementation of a circular buffer, which can also be used
|
||||
as a queue or stack.
|
||||
|
||||
Supported operations: `push`, `pop`, `append`.
|
||||
|
||||
To achieve thread safety (to some extent) in a producent-consumer situation,
|
||||
there is no length variable, only write pointers.
|
||||
|
||||
The buffer has a fixed size, values are copied when inserted. The buffer is created using `malloc`.
|
||||
If your platform does not support `malloc`, you will have to customize the initialization routine.
|
||||
|
||||
Applications
|
||||
------------
|
||||
|
||||
- Buffer for USARTs or similar communication interfaces
|
||||
- Event queue
|
||||
- Object stack
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
```c
|
||||
#include <stdint.h>
|
||||
#include "circbuf.h"
|
||||
|
||||
CircBuf *cb; // buffer instance
|
||||
|
||||
void main()
|
||||
{
|
||||
cb = cbuf_make(32, sizeof(char)); // create a char buffer of size 32.
|
||||
|
||||
// now it's ready for use!
|
||||
|
||||
// write functions return true on success:
|
||||
cbuf_append(cb, 'A'); // append A (using as a queue)
|
||||
cbuf_push(cb, 'B'); // push B (using as a stack)
|
||||
|
||||
// read all
|
||||
char c;
|
||||
while (cbuf_pop(cb, &c)) {
|
||||
printf("%c", c);
|
||||
}
|
||||
|
||||
cbuf_destroy(cb); // free the buffer
|
||||
}
|
||||
```
|
||||
|
||||
For details how to use each function, please read the header file.
|
||||
@@ -0,0 +1,167 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#include "circbuf.h"
|
||||
|
||||
// --- Circbuf data structure ----
|
||||
|
||||
/** Offset in void* buffer */
|
||||
#define PV_OFFS(pvBuf, elem_size, index) ((uint8_t*)(pvBuf) + ((elem_size)*(index)))
|
||||
|
||||
|
||||
// Instance structure
|
||||
struct circbuf_struct {
|
||||
void *buf;
|
||||
size_t elem_size;
|
||||
size_t cap;
|
||||
size_t lr; // last read pos
|
||||
size_t nw; // next write pos
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Write data to a CircBuf slot
|
||||
* @param cb : circbuf
|
||||
* @param index : slot index
|
||||
* @param source : data source
|
||||
*/
|
||||
static void write_buffer(CircBuf *cb, size_t index, const void *source)
|
||||
{
|
||||
memcpy(PV_OFFS(cb->buf, cb->elem_size, index), source, cb->elem_size);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Copy data from a CircBuf slot to a buffer
|
||||
* @param cb : circbuf
|
||||
* @param index : slot index
|
||||
* @param dest : destination buffer
|
||||
*/
|
||||
static void read_buffer(const CircBuf *cb, size_t index, void *dest)
|
||||
{
|
||||
memcpy(dest, PV_OFFS(cb->buf, cb->elem_size, index), cb->elem_size);
|
||||
}
|
||||
|
||||
|
||||
/** Create a cbuf */
|
||||
CircBuf *cbuf_make(size_t capacity, size_t elem_size)
|
||||
{
|
||||
// add one, because one is always unused.
|
||||
capacity++;
|
||||
|
||||
// Allocate the structure
|
||||
CircBuf *cb = malloc(sizeof(CircBuf));
|
||||
if (cb == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// allocate the buffer
|
||||
cb->buf = malloc(capacity * elem_size);
|
||||
if (cb->buf == NULL) {
|
||||
free(cb); // fail - free structure.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// set capacity, clear state
|
||||
cb->elem_size = elem_size;
|
||||
cb->cap = capacity;
|
||||
cbuf_clear(cb);
|
||||
|
||||
return cb;
|
||||
}
|
||||
|
||||
|
||||
/** Release cbuf memory */
|
||||
void cbuf_destroy(CircBuf *cb)
|
||||
{
|
||||
if (cb != NULL) {
|
||||
if (cb->buf != NULL) {
|
||||
free(cb->buf);
|
||||
}
|
||||
|
||||
free(cb);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Check if cbuf is full */
|
||||
bool cbuf_full(const CircBuf *cb)
|
||||
{
|
||||
if (cb == NULL) return false;
|
||||
|
||||
return (cb->lr == cb->nw);
|
||||
}
|
||||
|
||||
|
||||
/** Check if cbuf is empty */
|
||||
bool cbuf_empty(const CircBuf *cb)
|
||||
{
|
||||
if (cb == NULL) return true;
|
||||
|
||||
return ((cb->lr + 1) % cb->cap) == cb->nw;
|
||||
}
|
||||
|
||||
|
||||
/** Write a byte to the buffer, if space left */
|
||||
bool cbuf_append(CircBuf *cb, const void *source)
|
||||
{
|
||||
if (cb == NULL) return false;
|
||||
if (source == NULL) return false;
|
||||
if (cbuf_full(cb)) return false;
|
||||
|
||||
write_buffer(cb, cb->nw, source);
|
||||
|
||||
// increment
|
||||
cb->nw++;
|
||||
if (cb->nw == cb->cap) cb->nw = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/** Push value to the end, like a stack. */
|
||||
bool cbuf_push(CircBuf *cb, const void *source)
|
||||
{
|
||||
if (cb == NULL) return false;
|
||||
if (source == NULL) return false;
|
||||
if (cbuf_full(cb)) return false;
|
||||
|
||||
write_buffer(cb, cb->lr, source);
|
||||
|
||||
// move lr back
|
||||
if (cb->lr == 0) {
|
||||
cb->lr = cb->cap - 1; // wrap to the end
|
||||
} else {
|
||||
cb->lr--;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/** Read one byte, if not empty. */
|
||||
bool cbuf_pop(CircBuf *cb, void *dest)
|
||||
{
|
||||
if (cb == NULL || dest == NULL) return false;
|
||||
if (cbuf_empty(cb)) return false;
|
||||
|
||||
// increment
|
||||
cb->lr++;
|
||||
if (cb->lr == cb->cap) cb->lr = 0;
|
||||
|
||||
read_buffer(cb, cb->lr, dest);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/** Clear a cbuf */
|
||||
void cbuf_clear(CircBuf *cb)
|
||||
{
|
||||
if (cb == NULL) return;
|
||||
|
||||
cb->lr = cb->cap - 1;
|
||||
cb->nw = 0;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* @file circbuf.h
|
||||
* @author Ondřej Hruška, 2016
|
||||
*
|
||||
* Circular buffer / queue / stack.
|
||||
* Slots are pre-allocated, values are copied into the buffer.
|
||||
*
|
||||
* The buffer may be used as a stack, event queue or a simple buffer.
|
||||
*
|
||||
* -------------------------------------
|
||||
*
|
||||
* NW LR
|
||||
* append -> [][][][] -> pop
|
||||
* <- push
|
||||
*
|
||||
* NW - next write pointer (stack base)
|
||||
* LR - last read position (stack top)
|
||||
*
|
||||
* -------------------------------------
|
||||
*
|
||||
* MIT license
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
typedef struct circbuf_struct CircBuf;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Initialize a circular buffer. The buffer is malloc'd.
|
||||
* @param capacity : buffer capacity
|
||||
* @param elem_size : size of one element
|
||||
* @return pointer to the buffer instance
|
||||
*/
|
||||
CircBuf *cbuf_make(size_t capacity, size_t elem_size);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Destroy a buffer, freeing used memory.
|
||||
*
|
||||
* @attention
|
||||
* If the buffer items have malloc'd members, you have
|
||||
* to free them manually to avoid a memory leak.
|
||||
*
|
||||
* @param cb : buffer
|
||||
*/
|
||||
void cbuf_destroy(CircBuf *cb);
|
||||
|
||||
|
||||
/** Test for full buffer */
|
||||
bool cbuf_full(const CircBuf *cb);
|
||||
|
||||
|
||||
/** Test for empty buffer */
|
||||
bool cbuf_empty(const CircBuf *cb);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Append a value to the buffer (FIFO)
|
||||
* @param cb : buffer
|
||||
* @param source : pointer to a value (will be copied)
|
||||
* @return success
|
||||
*/
|
||||
bool cbuf_append(CircBuf *cb, const void *source);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Push a value into the circbuf (LIFO).
|
||||
*
|
||||
* @param cb : buffer
|
||||
* @param source : pointer to a value (will be copied)
|
||||
* @return success
|
||||
*/
|
||||
bool cbuf_push(CircBuf *cb, const void *source);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Read a value from the buffer, return susccess.
|
||||
*
|
||||
* @param cb : buffer
|
||||
* @param dest : read destionation. If NULL, value is discarded.
|
||||
* @return success
|
||||
*/
|
||||
bool cbuf_pop(CircBuf *cb, void *dest);
|
||||
|
||||
|
||||
/** @brief Remove all data from buffer */
|
||||
void cbuf_clear(CircBuf *cb);
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct {
|
||||
const char *pattern;
|
||||
size_t cursor;
|
||||
} matcher_t;
|
||||
|
||||
|
||||
/**
|
||||
* Consume an incoming character.
|
||||
* If this char was the last char of the pattern, returns true and rewinds.
|
||||
*
|
||||
* If the char is not in the pattern, resets match state.
|
||||
*
|
||||
* @returns true if the char concluded the expected pattern.
|
||||
*/
|
||||
bool matcher_test(matcher_t * mb, uint8_t b);
|
||||
@@ -0,0 +1,14 @@
|
||||
Character stream pattern matcher
|
||||
================================
|
||||
|
||||
Matcher can be used for detecting a pattern in a stream of characters.
|
||||
With each incoming character, call `matcher_test()`.
|
||||
|
||||
It will return true if the character completed a match.
|
||||
|
||||
Applications
|
||||
------------
|
||||
|
||||
This buffer was designed for parsing responses to AT commands from ESP8266.
|
||||
|
||||
It can be used for any similar purpose.
|
||||
@@ -5,6 +5,11 @@
|
||||
|
||||
#include "matcher.h"
|
||||
|
||||
void matcher_reset(matcher_t *m)
|
||||
{
|
||||
m->cursor = 0;
|
||||
}
|
||||
|
||||
|
||||
/** Handle incoming char. Returns true if this char completed the match. */
|
||||
bool matcher_test(matcher_t * m, uint8_t b)
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @file matcher.h
|
||||
* @author Ondřej Hruška, 2016
|
||||
*
|
||||
* String matching utility.
|
||||
*
|
||||
* Matcher can be used for detecting a pattern in a stream of characters.
|
||||
* With each incoming character, call matcher_test().
|
||||
*
|
||||
* It will return true if the character completed a match.
|
||||
*
|
||||
* MIT license
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct {
|
||||
const char *pattern;
|
||||
size_t cursor;
|
||||
} matcher_t;
|
||||
|
||||
|
||||
/** reset match progress */
|
||||
void matcher_reset(matcher_t *m);
|
||||
|
||||
|
||||
/**
|
||||
* Consume an incoming character.
|
||||
* If this char was the last char of the pattern, returns true and resets matcher.
|
||||
*
|
||||
* If the char is not in the pattern, resets matcher.
|
||||
*
|
||||
* @returns true if the char concluded the expected pattern.
|
||||
*/
|
||||
bool matcher_test(matcher_t * mb, uint8_t b);
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
float * buf; // buffer (allocated at init)
|
||||
size_t cap; // capacity
|
||||
size_t nw; // next write index
|
||||
float mean; // updated on write
|
||||
} meanbuf_t;
|
||||
|
||||
|
||||
/** Init a buffer */
|
||||
void meanbuf_init(meanbuf_t *mb, size_t size);
|
||||
|
||||
/** Deinit a buffer (free buffer array) */
|
||||
void meanbuf_deinit(meanbuf_t *mb);
|
||||
|
||||
/** Add a value to the buffer. Returns current mean. */
|
||||
float meanbuf_add(meanbuf_t *mb, float f);
|
||||
@@ -0,0 +1,11 @@
|
||||
Averaging float buffer
|
||||
======================
|
||||
|
||||
*(You can adjust it to use doubles, if you prefer.)*
|
||||
|
||||
The `meanbuf_create()` function allocates a buffer.
|
||||
|
||||
You can then call `meanbuf_add()` to add a new value into the buffer (and remove the oldest).
|
||||
This function returns the current average value.
|
||||
|
||||
This buffer can be used for **signal smoothing** (such as from an analogue sensor).
|
||||
@@ -4,9 +4,19 @@
|
||||
#include "meanbuf.h"
|
||||
|
||||
|
||||
struct meanbuf_struct {
|
||||
float * buf; // buffer (allocated at init)
|
||||
size_t cap; // capacity
|
||||
size_t nw; // next write index
|
||||
float mean; // updated on write
|
||||
};
|
||||
|
||||
|
||||
/** Init a buffer */
|
||||
void meanbuf_init(meanbuf_t *mb, size_t size)
|
||||
MeanBuf *meanbuf_create(size_t size)
|
||||
{
|
||||
MeanBuf *mb = malloc(sizeof(MeanBuf));
|
||||
|
||||
if (size < 1) size = 1;
|
||||
|
||||
mb->buf = calloc(size, sizeof(float)); // calloc, so it starts with zeros.
|
||||
@@ -18,19 +28,25 @@ void meanbuf_init(meanbuf_t *mb, size_t size)
|
||||
for (uint16_t i = 0; i < size; i++) {
|
||||
mb->buf[i] = 0;
|
||||
}
|
||||
|
||||
return mb;
|
||||
}
|
||||
|
||||
|
||||
void meanbuf_deinit(meanbuf_t *mb)
|
||||
void meanbuf_destroy(MeanBuf *mb)
|
||||
{
|
||||
if (mb == NULL) return;
|
||||
|
||||
if (mb->buf != NULL) {
|
||||
free(mb->buf);
|
||||
}
|
||||
|
||||
free(mb);
|
||||
}
|
||||
|
||||
|
||||
/** Add a value to the buffer. Returns current mean. */
|
||||
float meanbuf_add(meanbuf_t *mb, float f)
|
||||
float meanbuf_add(MeanBuf *mb, float f)
|
||||
{
|
||||
// add sample
|
||||
mb->buf[mb->nw++] = f;
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @file meanbuf.h
|
||||
* @author Ondřej Hruška, 2016
|
||||
*
|
||||
* Averaging float buffer. (You can adjust it to use doubles, if you prefer.)
|
||||
*
|
||||
* The meanbuf_create() function allocates a buffer.
|
||||
*
|
||||
* You can then call meanbuf_add() to add a new value into the buffer (and remove the oldest).
|
||||
* This function returns the current average value.
|
||||
*
|
||||
* This buffer can be used for signal smoothing (such as from an analogue sensor).
|
||||
*
|
||||
* MIT license
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct meanbuf_struct MeanBuf;
|
||||
|
||||
/** Init a buffer */
|
||||
MeanBuf *meanbuf_create(size_t size);
|
||||
|
||||
/** Deinit a buffer (free buffer array) */
|
||||
void meanbuf_destroy(MeanBuf *mb);
|
||||
|
||||
/** Add a value to the buffer. Returns current mean. */
|
||||
float meanbuf_add(MeanBuf *mb, float f);
|
||||
Reference in New Issue
Block a user