Browse Source

make headers work in C++

add two more variable length integer encoding functions
master
Felix von Leitner 9 years ago
parent
commit
cf46170dc7
  1. 8
      array.h
  2. 8
      buffer.h
  3. 8
      byte.h
  4. 8
      case.h
  5. 8
      cdb.h
  6. 8
      cdb_make.h
  7. 8
      dns.h
  8. 8
      errmsg.h
  9. 12
      fmt.h
  10. 5
      fmt/fmt_asn1derlength.3
  11. 18
      fmt/fmt_asn1dertag.3
  12. 15
      fmt/fmt_asn1dertag.c
  13. 11
      io.h
  14. 26
      io_internal.h
  15. 8
      iob.h
  16. 8
      ip4.h
  17. 8
      ip6.h
  18. 8
      mmap.h
  19. 8
      ndelay.h
  20. 8
      open.h
  21. 8
      openreadclose.h
  22. 8
      rangecheck.h
  23. 8
      readclose.h
  24. 8
      safemult.h
  25. 11
      scan.h
  26. 14
      scan/scan_asn1derlength.3
  27. 20
      scan/scan_asn1dertag.3
  28. 14
      scan/scan_asn1dertag.c
  29. 8
      socket.h
  30. 9
      str.h
  31. 9
      stralloc.h
  32. 8
      tai.h
  33. 8
      taia.h
  34. 8
      textcode.h
  35. 8
      uint16.h
  36. 8
      uint32.h
  37. 8
      uint64.h
  38. 8
      windoze.h

8
array.h

@ -5,6 +5,10 @@
#include <stddef.h>
#include "uint64.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
char* p;
int64 allocated; /* in bytes */
@ -40,4 +44,8 @@ void array_chop(array* x,uint64 membersize,uint64 members);
#define array_failed(x) (array_bytes(x)==-1)
#define array_unallocated(x) (array_bytes(x)==0)
#ifdef __cplusplus
}
#endif
#endif

8
buffer.h

@ -7,6 +7,10 @@
/* for ssize_t: */
#include <sys/types.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct buffer {
char *x; /* actual buffer space */
size_t p; /* current position */
@ -139,4 +143,8 @@ void buffer_fromsa(buffer* b,stralloc* sa); /* read from sa */
int buffer_tosa(buffer*b,stralloc* sa); /* write to sa, auto-growing it */
#endif
#ifdef __cplusplus
}
#endif
#endif

8
byte.h

@ -5,6 +5,10 @@
/* for size_t: */
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef __pure__
#define __pure__
#endif
@ -37,4 +41,8 @@ void byte_zero(void* out, size_t len);
#define byte_equal(s,n,t) (!byte_diff((s),(n),(t)))
#ifdef __cplusplus
}
#endif
#endif

8
case.h

@ -4,6 +4,10 @@
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* turn upper case letters to lower case letters, ASCIIZ */
void case_lowers(char *s);
/* turn upper case letters to lower case letters, binary */
@ -20,4 +24,8 @@ int case_starts(const char *,const char *);
#define case_equals(s,t) (!case_diffs((s),(t)))
#define case_equalb(s,n,t) (!case_diffb((s),(n),(t)))
#ifdef __cplusplus
}
#endif
#endif

8
cdb.h

@ -5,6 +5,10 @@
#include "uint32.h"
#include "uint64.h"
#ifdef __cplusplus
extern "C" {
#endif
#define CDB_HASHSTART 5381
extern uint32 cdb_hashadd(uint32 h,unsigned char c);
extern uint32 cdb_hash(const unsigned char *buf,unsigned long int len);
@ -41,4 +45,8 @@ extern int cdb_successor(struct cdb *c,const unsigned char *,unsigned long int);
#define cdb_keypos(c) ((c)->kpos)
#define cdb_keylen(c) ((c)->dpos-(c)->kpos)
#ifdef __cplusplus
}
#endif
#endif

8
cdb_make.h

@ -6,6 +6,10 @@
#include "uint64.h"
#include "uint32.h"
#ifdef __cplusplus
extern "C" {
#endif
#define CDB_HPLIST 1000
struct cdb_hp { uint32 h; uint32 p; } ;
@ -36,4 +40,8 @@ extern int cdb_make_addend(struct cdb_make *,unsigned long int,unsigned long int
extern int cdb_make_add(struct cdb_make *,const unsigned char *,unsigned long int,const unsigned char *,unsigned long int);
extern int cdb_make_finish(struct cdb_make *);
#ifdef __cplusplus
}
#endif
#endif

8
dns.h

@ -6,6 +6,10 @@
#include "iopause.h"
#include "taia.h"
#ifdef __cplusplus
extern "C" {
#endif
#define DNS_C_IN "\0\1"
#define DNS_C_ANY "\0\377"
@ -92,4 +96,8 @@ void dns_name6_domain(char *,const char *);
#define DNS_NAME6_DOMAIN (4*16+11)
int dns_name6(stralloc *,const char *);
#ifdef __cplusplus
}
#endif
#endif

8
errmsg.h

@ -5,6 +5,10 @@
/* for exit(): */
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
/* These use file descriptor 2, not buffer_2!
* Call buffer_flush(buffer_2) before calling these! */
@ -27,4 +31,8 @@ void errmsg_infosys(const char* message, ...);
#define msg(...) errmsg_info(__VA_ARGS__,(char*)0);
#define msgsys(...) errmsg_infosys(__VA_ARGS__,(char*)0);
#ifdef __cplusplus
}
#endif
#endif

12
fmt.h

@ -9,6 +9,10 @@
/* for time_t: */
#include <sys/types.h>
#ifdef __cplusplus
extern "C" {
#endif
#define FMT_LONG 41 /* enough space to hold -2^127 in decimal, plus \0 */
#define FMT_ULONG 40 /* enough space to hold 2^128 - 1 in decimal, plus \0 */
#define FMT_8LONG 44 /* enough space to hold 2^128 - 1 in octal, plus \0 */
@ -92,9 +96,11 @@ size_t fmt_httpdate(char* dest,time_t t);
#define FMT_UTF8 5
#define FMT_ASN1LENGTH 17 /* enough space to hold 2^128-1 */
#define FMT_ASN1TAG 19 /* enough space to hold 2^128-1 */
/* some variable length encodings for integers */
size_t fmt_utf8(char* dest,uint32_t n); /* can store 0-0x7fffffff */
size_t fmt_asn1derlength(char* dest,unsigned long long l);
size_t fmt_asn1derlength(char* dest,unsigned long long l); /* 0-0x7f: 1 byte, above that 1+bytes_needed bytes */
size_t fmt_asn1dertag(char* dest,unsigned long long l); /* 1 byte for each 7 bits; upper bit = more bytes coming */
/* internal functions, may be independently useful */
char fmt_tohex(char c);
@ -107,4 +113,8 @@ size_t fmt_strm_internal(char* dest,...);
#endif
#define fmt_strm_alloca(a,...) ({ size_t len=fmt_strm((char*)0,a,__VA_ARGS__)+1; char* c=(len<MAX_ALLOCA?alloca(len):0); if (c) c[fmt_strm(c,a,__VA_ARGS__)]=0; c;})
#ifdef __cplusplus
}
#endif
#endif

5
fmt/fmt_asn1derlength.3

@ -6,8 +6,9 @@ fmt_asn1derlength \- encode unsigned integer like ASN.1 DER length
size_t \fBfmt_asn1derlength\fP(char *\fIdest\fR,unsigned long long \fIsource\fR);
.SH DESCRIPTION
fmt_asn1derlength encodes an unsigned integer using the UTF-8 rules. This
can take from 1 byte (0-0x7f) up to sizeof(source)+1 bytes.
fmt_asn1derlength encodes an unsigned integer using the ASN.1 DER
for encoding tag lengths. This can take from 1 byte (0-0x7f) up to
sizeof(source)+1 bytes.
If \fIdest\fR equals FMT_LEN (i.e. is NULL), fmt_asn1derlength returns the
number of bytes it would have written.

18
fmt/fmt_asn1dertag.3

@ -0,0 +1,18 @@
.TH fmt_asn1dertag 3
.SH NAME
fmt_asn1dertag \- encode unsigned integer like ASN.1 DER tag
.SH SYNTAX
.B #include <fmt.h>
size_t \fBfmt_asn1dertag\fP(char *\fIdest\fR,unsigned long long \fIsource\fR);
.SH DESCRIPTION
fmt_asn1dertag encodes an unsigned integer using the ASN.1 DER for
encoding tag. This takes one byte for every 7 bits in the number.
If \fIdest\fR equals FMT_LEN (i.e. is NULL), fmt_asn1dertag returns the
number of bytes it would have written.
For convenience, fmt.h defines the integer FMT_ASN1TAG to be big
enough to contain every possible fmt_asn1dertag output.
.SH "SEE ALSO"
scan_asn1dertag(3)

15
fmt/fmt_asn1dertag.c

@ -0,0 +1,15 @@
#include "fmt.h"
/* write int in least amount of bytes, return number of bytes */
/* as used in ASN.1 DER tag */
size_t fmt_asn1dertag(char* dest,unsigned long long l) {
/* encoding is either l%128 or (0x80+number of bytes,bytes) */
size_t n=0,i;
unsigned long long t;
for (t=l, n=1; t>0x7f; t>>=7) ++n;
for (i=0; i<n; ++i) {
if (dest) dest[n-i-1]=((i!=0)<<7) | (l&0x7f);
l>>=7;
}
return i;
}

11
io.h

@ -7,6 +7,10 @@
#include "uint64.h"
#include "taia.h"
#ifdef __cplusplus
extern "C" {
#endif
/* like open(s,O_RDONLY) */
/* return 1 if ok, 0 on error */
int io_readfile(int64* d,const char* s);
@ -72,7 +76,8 @@ int64 io_canwrite();
int64 io_timeouted();
/* put d on internal data structure, return 1 on success, 0 on error */
int io_fd(int64 d);
int io_fd(int64 d); /* use this for sockets before you called connect() or accept() */
int io_fd_connected(int64 d); /* use this for connected sockets (assumes socket is writable) */
void io_setcookie(int64 d,void* cookie);
void* io_getcookie(int64 d);
@ -115,4 +120,8 @@ int64 io_mmapwritefile(int64 out,int64 in,uint64 off,uint64 bytes,io_write_callb
#include_next <io.h>
#endif
#ifdef __cplusplus
}
#endif
#endif

26
io_internal.h

@ -18,14 +18,32 @@ my_extern HANDLE io_comport;
#endif
#endif
/* We simulate a level-triggered API on top of an event signalling
* mechanism that can be level-triggered (epoll/kqueue/poll) or
* edge-triggered (SIGIO).
* Difficulty: we want to avoid unnecessary syscalls, so we keep state
* internally. If the user says he does not want to read/write anymore,
* we don't tell the kernel straight away. The rationale is that the
* typical protocol consists of interleaved reading and writing, so
* after each read you'd call io_dontwantread, io_wantwrite, io_wait,
* io_dontwantwrite, io_wantread, and in the regular case there is no
* incoming data between io_dontwantread and io_wantread, so we might as
* well optimistically not do those syscalls and then handle the
* complexity if there is more incoming data. */
/* Basically, we tell the kernel that we want to read if !canread,
* and we tell the kernel that we want to write if !canwrite. */
typedef struct {
tai6464 timeout;
unsigned int wantread:1;
unsigned int wantread:1; /* does the app want to read/write? */
unsigned int wantwrite:1;
unsigned int canread:1;
unsigned int canread:1; /* do we know we can read/write? */
unsigned int canwrite:1;
unsigned int nonblock:1;
unsigned int inuse:1;
unsigned int nonblock:1; /* is this socket non-blocking? */
unsigned int inuse:1; /* internal consistency checking */
unsigned int kernelwantread:1; /* did we tell the kernel we want to read/write? */
unsigned int kernelwantwrite:1;
#ifdef __MINGW32__
unsigned int readqueued:2;
unsigned int writequeued:2;

8
iob.h

@ -16,6 +16,10 @@
#include "io.h"
#include "array.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct io_batch {
array b;
uint64 bytesleft;
@ -37,4 +41,8 @@ void iob_free(io_batch* b);
void iob_prefetch(io_batch* b,uint64 bytes);
uint64 iob_bytesleft(const io_batch* b);
#ifdef __cplusplus
}
#endif
#endif

8
ip4.h

@ -2,6 +2,10 @@
#ifndef IP4_H
#define IP4_H
#ifdef __cplusplus
extern "C" {
#endif
unsigned int scan_ip4(const char *src,char *ip);
unsigned int fmt_ip4(char *dest,const char *ip);
@ -14,4 +18,8 @@ unsigned int fmt_ip4(char *dest,const char *ip);
extern const char ip4loopback[4]; /* = {127,0,0,1};*/
#ifdef __cplusplus
}
#endif
#endif

8
ip6.h

@ -5,6 +5,10 @@
#include "byte.h"
#include "uint32.h"
#ifdef __cplusplus
extern "C" {
#endif
unsigned int scan_ip6(const char* src,char* ip);
unsigned int fmt_ip6(char* dest,const char* ip);
unsigned int fmt_ip6c(char* dest,const char* ip);
@ -33,4 +37,8 @@ extern const char V6any[16]; /*={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; */
#define ip6_isv4mapped(ip) (byte_equal(ip,12,V4mappedprefix))
#ifdef __cplusplus
}
#endif
#endif

8
mmap.h

@ -4,6 +4,10 @@
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* open file for reading, mmap whole file, close file, write length of
* map in filesize and return pointer to map. */
char* mmap_read(const char *filename,size_t* filesize);
@ -20,4 +24,8 @@ char* mmap_shared(const char *filename,size_t* filesize);
/* unmap a mapped region */
int mmap_unmap(char* mapped,size_t maplen);
#ifdef __cplusplus
}
#endif
#endif

8
ndelay.h

@ -2,7 +2,15 @@
#ifndef NDELAY_H
#define NDELAY_H
#ifdef __cplusplus
extern "C" {
#endif
int ndelay_on(int);
int ndelay_off(int);
#ifdef __cplusplus
}
#endif
#endif

8
open.h

@ -2,6 +2,10 @@
#ifndef OPEN_H
#define OPEN_H
#ifdef __cplusplus
extern "C" {
#endif
/* open filename for reading and return the file handle or -1 on error */
int open_read(const char* filename);
@ -29,4 +33,8 @@ int open_write(const char* filename);
* Return file handle or -1 on error. */
int open_rw(const char* filename);
#ifdef __cplusplus
}
#endif
#endif

8
openreadclose.h

@ -4,6 +4,14 @@
#include "stralloc.h"
#ifdef __cplusplus
extern "C" {
#endif
int openreadclose(const char *filename,stralloc *buf,size_t initiallength);
#ifdef __cplusplus
}
#endif
#endif

8
rangecheck.h

@ -5,6 +5,10 @@
#include <inttypes.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* We are trying to achieve that gcc has to inline the function and we
* don't want it to emit a copy of the function. This can be done with
* static inline or with extern inline. static inline tells gcc to not
@ -151,4 +155,8 @@ int range_str4inbuf(const void* buf,size_t len,const void* stringstart);
#undef __static
#ifdef __cplusplus
}
#endif
#endif

8
readclose.h

@ -3,7 +3,15 @@
#include "stralloc.h"
#ifdef __cplusplus
extern "C" {
#endif
int readclose_append(int fd,stralloc *buf,size_t initlen);
int readclose(int fd,stralloc *buf,size_t initlen);
#ifdef __cplusplus
}
#endif
#endif

8
safemult.h

@ -6,6 +6,10 @@
#include "uint32.h"
#include "uint64.h"
#ifdef __cplusplus
extern "C" {
#endif
/* return 0 for overflow, 1 for ok */
int umult16(uint16 a,uint16 b,uint16* c);
int imult16( int16 a, int16 b, int16* c);
@ -16,4 +20,8 @@ int imult32( int32 a, int32 b, int32* c);
int umult64(uint64 a,uint64 b,uint64* c);
int imult64( int64 a, int64 b, int64* c);
#ifdef __cplusplus
}
#endif
#endif

11
scan.h

@ -4,9 +4,15 @@
/* for size_t: */
#include <stddef.h>
/* for uint32_t: */
#include <stdint.h>
/* for time_t: */
#include <sys/types.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef __pure__
#define __pure__
#endif
@ -76,9 +82,14 @@ size_t scan_httpdate(const char *in,time_t *t) __pure__;
/* some variable length encodings for integers */
size_t scan_utf8(const char* in,size_t len,uint32_t* n) __pure__;
size_t scan_asn1derlength(const char* in,size_t len,unsigned long long* n) __pure__;
size_t scan_asn1dertag(const char* in,size_t len,unsigned long long* n) __pure__;
/* a few internal function that might be useful independently */
/* convert from hex ASCII, return 0 to 15 for success or -1 for failure */
int scan_fromhex(unsigned char c);
#ifdef __cplusplus
}
#endif
#endif

14
scan/scan_asn1derlength.3

@ -1,20 +1,20 @@
.TH scan_asn1length 3
.TH scan_asn1derlength 3
.SH NAME
scan_asn1length \- decode an unsigned integer from ASN.1 DER length encoding
scan_asn1derlength \- decode an unsigned integer from ASN.1 DER length encoding
.SH SYNTAX
.B #include <scan.h>
size_t \fBscan_asn1length\fP(const char *\fIsrc\fR,size_t \fIlen\fR,unsigned long long *\fIdest\fR);
size_t \fBscan_asn1derlength\fP(const char *\fIsrc\fR,size_t \fIlen\fR,unsigned long long *\fIdest\fR);
.SH DESCRIPTION
scan_asn1length decodes an unsigned integer in ASN.1 DER length encoding
scan_asn1derlength decodes an unsigned integer in ASN.1 DER length encoding
from a memory area holding binary data. It writes the decode value in
\fIdest\fR and returns the number of bytes it read from \fIsrc\fR.
scan_asn1length never reads more than \fIlen\fR bytes from \fIsrc\fR. If the
scan_asn1derlength never reads more than \fIlen\fR bytes from \fIsrc\fR. If the
sequence is longer than that, or the memory area contains an invalid
sequence, scan_asn1length returns 0 and does not touch \fIdest\fR.
sequence, scan_asn1derlength returns 0 and does not touch \fIdest\fR.
The length of the longest ASN.1 DER length sequence is 128 bytes. In
practice the largest sequence is sizeof(*dest)+1.
.SH "SEE ALSO"
fmt_asn1length(3)
fmt_asn1derlength(3)

20
scan/scan_asn1dertag.3

@ -0,0 +1,20 @@
.TH scan_asn1dertag 3
.SH NAME
scan_asn1dertag \- decode an unsigned integer from ASN.1 DER length encoding
.SH SYNTAX
.B #include <scan.h>
size_t \fBscan_asn1dertag\fP(const char *\fIsrc\fR,size_t \fIlen\fR,unsigned long long *\fIdest\fR);
.SH DESCRIPTION
scan_asn1dertag decodes an unsigned integer in ASN.1 DER tag encoding
from a memory area holding binary data. It writes the decode value in
\fIdest\fR and returns the number of bytes it read from \fIsrc\fR.
scan_asn1dertag never reads more than \fIlen\fR bytes from \fIsrc\fR. If the
sequence is longer than that, or the memory area contains an invalid
sequence, scan_asn1dertag returns 0 and does not touch \fIdest\fR.
The length of the longest ASN.1 DER length sequence is 128 bytes. In
practice the largest sequence is sizeof(*dest)+1.
.SH "SEE ALSO"
fmt_asn1dertag(3)

14
scan/scan_asn1dertag.c

@ -0,0 +1,14 @@
#include "scan.h"
size_t scan_asn1dertag(const char* src,size_t len,unsigned long long* length) {
size_t n;
unsigned long long l=0;
for (n=0; n<len; ++n) {
l=(l<<7) | (src[n]&0x7f);
if (!(src[n]&0x80)) {
*length=l;
return n+1;
}
}
return 0;
}

8
socket.h

@ -7,6 +7,10 @@
#include "uint16.h"
#include "uint32.h"
#ifdef __cplusplus
extern "C" {
#endif
int socket_tcp4(void);
int socket_tcp4b(void);
int socket_udp4(void);
@ -117,4 +121,8 @@ extern int noipv6;
#endif
#ifdef __cplusplus
}
#endif
#endif

9
str.h

@ -3,6 +3,11 @@
#define STR_H
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef __pure__
#define __pure__
#endif
@ -46,4 +51,8 @@ int str_start(const char *a,const char *b) __pure__;
/* convenience shortcut to test for string equality */
#define str_equal(s,t) (!str_diff((s),(t)))
#ifdef __cplusplus
}
#endif
#endif

9
stralloc.h

@ -3,6 +3,11 @@
#define STRALLOC_H
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef __pure__
#define __pure__
#endif
@ -158,4 +163,8 @@ int buffer_get_new_token_sa_pred(buffer* b,stralloc* sa,sa_predicate p);
void buffer_fromsa(buffer* b,stralloc* sa);
#endif
#ifdef __cplusplus
}
#endif
#endif

8
tai.h

@ -6,6 +6,10 @@
#include "uint64.h"
#ifdef __cplusplus
extern "C" {
#endif
/* A struct tai value is an integer between 0 inclusive and 2^64
* exclusive. The format of struct tai is designed to speed up common
* operations; applications should not look inside struct tai.
@ -58,4 +62,8 @@ void tai_unpack(const char *,struct tai *);
void tai_uint(struct tai *,unsigned int);
#ifdef __cplusplus
}
#endif
#endif

8
taia.h

@ -7,6 +7,10 @@
#include "tai.h"
#include "uint32.h"
#ifdef __cplusplus
extern "C" {
#endif
/* A struct taia value is a number between 0 inclusive and 2^64
* exclusive. The number is a multiple of 10^-18. The format of struct
* taia is designed to speed up common operations; applications should
@ -67,4 +71,8 @@ unsigned int taia_fmtfrac(char *s,const tai6464 *t);
/* initialize t to secs seconds. */
void taia_uint(tai6464 *t,unsigned int secs);
#ifdef __cplusplus
}
#endif
#endif

8
textcode.h

@ -4,6 +4,10 @@
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* These take len bytes from src and write them in encoded form to
* dest (if dest != NULL), returning the number of bytes written. */
@ -111,4 +115,8 @@ size_t scan_tofrom_array(size_t (*func)(const char*,char*,size_t*),
extern const char base64[64];
#ifdef __cplusplus
}
#endif
#endif

8
uint16.h

@ -4,6 +4,10 @@
#include <inttypes.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef uint16_t uint16;
typedef int16_t int16;
@ -35,4 +39,8 @@ uint16 uint16_read_big(const char *in);
#endif
#ifdef __cplusplus
}
#endif
#endif

8
uint32.h

@ -4,6 +4,10 @@
#include <inttypes.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef uint32_t uint32;
typedef int32_t int32;
@ -35,4 +39,8 @@ uint32 uint32_read_big(const char *in);
#endif
#ifdef __cplusplus
}
#endif
#endif

8
uint64.h

@ -4,6 +4,10 @@
#include <inttypes.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef uint64_t uint64;
typedef int64_t int64;
@ -25,4 +29,8 @@ uint64 uint64_read_big(const char *in);
#endif
#ifdef __cplusplus
}
#endif
#endif

8
windoze.h

@ -1,5 +1,9 @@
#ifdef __MINGW32__
#ifdef __cplusplus
extern "C" {
#endif
/* set errno to WSAGetLastError() */
int winsock2errno(long l);
void __winsock_init(void);
@ -9,4 +13,8 @@ void __winsock_init(void);
#define winsock2errno(fnord) (fnord)
#define __winsock_init()
#ifdef __cplusplus
}
#endif
#endif
Loading…
Cancel
Save