ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/CBOR-XS/XS.xs
(Generate patch)

Comparing cvsroot/CBOR-XS/XS.xs (file contents):
Revision 1.36 by root, Sat Nov 30 17:37:45 2013 UTC vs.
Revision 1.50 by root, Thu Feb 25 02:29:22 2016 UTC

7#include <stdlib.h> 7#include <stdlib.h>
8#include <stdio.h> 8#include <stdio.h>
9#include <limits.h> 9#include <limits.h>
10#include <float.h> 10#include <float.h>
11 11
12#define ECB_NO_THREADS 1
12#include "ecb.h" 13#include "ecb.h"
13 14
14// compatibility with perl <5.18 15// compatibility with perl <5.18
15#ifndef HvNAMELEN_get 16#ifndef HvNAMELEN_get
16# define HvNAMELEN_get(hv) strlen (HvNAME (hv)) 17# define HvNAMELEN_get(hv) strlen (HvNAME (hv))
97}; 98};
98 99
99#define F_SHRINK 0x00000001UL 100#define F_SHRINK 0x00000001UL
100#define F_ALLOW_UNKNOWN 0x00000002UL 101#define F_ALLOW_UNKNOWN 0x00000002UL
101#define F_ALLOW_SHARING 0x00000004UL 102#define F_ALLOW_SHARING 0x00000004UL
103#define F_ALLOW_CYCLES 0x00000008UL
102#define F_PACK_STRINGS 0x00000008UL 104#define F_PACK_STRINGS 0x00000010UL
105#define F_VALIDATE_UTF8 0x00000020UL
103 106
104#define INIT_SIZE 32 // initial scalar size to be allocated 107#define INIT_SIZE 32 // initial scalar size to be allocated
105 108
106#define SB do { 109#define SB do {
107#define SE } while (0) 110#define SE } while (0)
126typedef struct { 129typedef struct {
127 U32 flags; 130 U32 flags;
128 U32 max_depth; 131 U32 max_depth;
129 STRLEN max_size; 132 STRLEN max_size;
130 SV *filter; 133 SV *filter;
134
135 // for the incremental parser
136 STRLEN incr_pos; // the current offset into the text
137 STRLEN incr_need; // minimum bytes needed to decode
138 AV *incr_count; // for every nesting level, the number of outstanding values, or -1 for indef.
131} CBOR; 139} CBOR;
132 140
133ecb_inline void 141ecb_inline void
134cbor_init (CBOR *cbor) 142cbor_init (CBOR *cbor)
135{ 143{
139 147
140ecb_inline void 148ecb_inline void
141cbor_free (CBOR *cbor) 149cbor_free (CBOR *cbor)
142{ 150{
143 SvREFCNT_dec (cbor->filter); 151 SvREFCNT_dec (cbor->filter);
152 SvREFCNT_dec (cbor->incr_count);
144} 153}
145 154
146///////////////////////////////////////////////////////////////////////////// 155/////////////////////////////////////////////////////////////////////////////
147// utility functions 156// utility functions
148 157
315 324
316 ++enc->depth; 325 ++enc->depth;
317 326
318 encode_uint (enc, MAJOR_ARRAY, len + 1); 327 encode_uint (enc, MAJOR_ARRAY, len + 1);
319 328
329 if (SvMAGICAL (av))
320 for (i = 0; i <= len; ++i) 330 for (i = 0; i <= len; ++i)
321 { 331 {
322 SV **svp = av_fetch (av, i, 0); 332 SV **svp = av_fetch (av, i, 0);
323 encode_sv (enc, svp ? *svp : &PL_sv_undef); 333 encode_sv (enc, svp ? *svp : &PL_sv_undef);
324 } 334 }
335 else
336 for (i = 0; i <= len; ++i)
337 {
338 SV *sv = AvARRAY (av)[i];
339 encode_sv (enc, sv ? sv : &PL_sv_undef);
340 }
325 341
326 --enc->depth; 342 --enc->depth;
327} 343}
328 344
329static void 345static void
433 449
434 if ((method = gv_fetchmethod_autoload (stash, "TO_CBOR", 0))) 450 if ((method = gv_fetchmethod_autoload (stash, "TO_CBOR", 0)))
435 { 451 {
436 dSP; 452 dSP;
437 453
438 ENTER; SAVETMPS; PUSHMARK (SP); 454 ENTER; SAVETMPS;
455 PUSHMARK (SP);
439 // we re-bless the reference to get overload and other niceties right 456 // we re-bless the reference to get overload and other niceties right
440 XPUSHs (sv_bless (sv_2mortal (newRV_inc (sv)), stash)); 457 XPUSHs (sv_bless (sv_2mortal (newRV_inc (sv)), stash));
441 458
442 PUTBACK; 459 PUTBACK;
443 // G_SCALAR ensures that return value is 1 460 // G_SCALAR ensures that return value is 1
456 } 473 }
457 else if ((method = gv_fetchmethod_autoload (stash, "FREEZE", 0)) != 0) 474 else if ((method = gv_fetchmethod_autoload (stash, "FREEZE", 0)) != 0)
458 { 475 {
459 dSP; 476 dSP;
460 477
461 ENTER; SAVETMPS; PUSHMARK (SP); 478 ENTER; SAVETMPS;
479 SAVESTACK_POS ();
480 PUSHMARK (SP);
462 EXTEND (SP, 2); 481 EXTEND (SP, 2);
463 // we re-bless the reference to get overload and other niceties right 482 // we re-bless the reference to get overload and other niceties right
464 PUSHs (sv_bless (sv_2mortal (newRV_inc (sv)), stash)); 483 PUSHs (sv_bless (sv_2mortal (newRV_inc (sv)), stash));
465 PUSHs (sv_cbor); 484 PUSHs (sv_cbor);
466 485
568} 587}
569 588
570static SV * 589static SV *
571encode_cbor (SV *scalar, CBOR *cbor) 590encode_cbor (SV *scalar, CBOR *cbor)
572{ 591{
573 enc_t enc = { }; 592 enc_t enc = { 0 };
574 593
575 enc.cbor = *cbor; 594 enc.cbor = *cbor;
576 enc.sv = sv_2mortal (NEWSV (0, INIT_SIZE)); 595 enc.sv = sv_2mortal (NEWSV (0, INIT_SIZE));
577 enc.cur = SvPVX (enc.sv); 596 enc.cur = SvPVX (enc.sv);
578 enc.end = SvEND (enc.sv); 597 enc.end = SvEND (enc.sv);
728{ 747{
729 // for speed reasons, we specialcase single-string 748 // for speed reasons, we specialcase single-string
730 // byte or utf-8 strings as keys, but only when !stringref 749 // byte or utf-8 strings as keys, but only when !stringref
731 750
732 if (ecb_expect_true (!dec->stringref)) 751 if (ecb_expect_true (!dec->stringref))
733 if (ecb_expect_true ((*dec->cur - MAJOR_BYTES) <= LENGTH_EXT8)) 752 if (ecb_expect_true ((U8)(*dec->cur - MAJOR_BYTES) <= LENGTH_EXT8))
734 { 753 {
735 I32 len = decode_uint (dec); 754 I32 len = decode_uint (dec);
736 char *key = (char *)dec->cur; 755 char *key = (char *)dec->cur;
737 756
757 WANT (len);
738 dec->cur += len; 758 dec->cur += len;
739 759
740 if (ecb_expect_false (dec->stringref))
741 av_push (dec->stringref, newSVpvn (key, len));
742
743 hv_store (hv, key, len, decode_sv (dec), 0); 760 hv_store (hv, key, len, decode_sv (dec), 0);
744 761
745 return; 762 return;
746 } 763 }
747 else if (ecb_expect_true ((*dec->cur - MAJOR_TEXT) <= LENGTH_EXT8)) 764 else if (ecb_expect_true ((U8)(*dec->cur - MAJOR_TEXT) <= LENGTH_EXT8))
748 { 765 {
749 I32 len = decode_uint (dec); 766 I32 len = decode_uint (dec);
750 char *key = (char *)dec->cur; 767 char *key = (char *)dec->cur;
751 768
769 WANT (len);
752 dec->cur += len; 770 dec->cur += len;
753 771
754 if (ecb_expect_false (dec->stringref)) 772 if (ecb_expect_false (dec->cbor.flags & F_VALIDATE_UTF8))
755 av_push (dec->stringref, newSVpvn_utf8 (key, len, 1)); 773 if (!is_utf8_string (key, len))
774 ERR ("corrupted CBOR data (invalid UTF-8 in map key)");
756 775
757 hv_store (hv, key, -len, decode_sv (dec), 0); 776 hv_store (hv, key, -len, decode_sv (dec), 0);
758 777
759 return; 778 return;
760 } 779 }
762 SV *k = decode_sv (dec); 781 SV *k = decode_sv (dec);
763 SV *v = decode_sv (dec); 782 SV *v = decode_sv (dec);
764 783
765 hv_store_ent (hv, k, v, 0); 784 hv_store_ent (hv, k, v, 0);
766 SvREFCNT_dec (k); 785 SvREFCNT_dec (k);
786
787fail:
788 ;
767} 789}
768 790
769static SV * 791static SV *
770decode_hv (dec_t *dec) 792decode_hv (dec_t *dec)
771{ 793{
853 && SvCUR (sv) >= minimum_string_length (AvFILLp (dec->stringref) + 1)) 875 && SvCUR (sv) >= minimum_string_length (AvFILLp (dec->stringref) + 1))
854 av_push (dec->stringref, SvREFCNT_inc_NN (sv)); 876 av_push (dec->stringref, SvREFCNT_inc_NN (sv));
855 } 877 }
856 878
857 if (utf8) 879 if (utf8)
880 {
881 if (ecb_expect_false (dec->cbor.flags & F_VALIDATE_UTF8))
882 if (!is_utf8_string (SvPVX (sv), SvCUR (sv)))
883 ERR ("corrupted CBOR data (invalid UTF-8 in text string)");
884
858 SvUTF8_on (sv); 885 SvUTF8_on (sv);
886 }
859 887
860 return sv; 888 return sv;
861 889
862fail: 890fail:
863 SvREFCNT_dec (sv); 891 SvREFCNT_dec (sv);
912 case CBOR_TAG_VALUE_SHAREABLE: 940 case CBOR_TAG_VALUE_SHAREABLE:
913 { 941 {
914 if (ecb_expect_false (!dec->shareable)) 942 if (ecb_expect_false (!dec->shareable))
915 dec->shareable = (AV *)sv_2mortal ((SV *)newAV ()); 943 dec->shareable = (AV *)sv_2mortal ((SV *)newAV ());
916 944
945 if (dec->cbor.flags & F_ALLOW_CYCLES)
946 {
917 sv = newSV (0); 947 sv = newSV (0);
918 av_push (dec->shareable, SvREFCNT_inc_NN (sv)); 948 av_push (dec->shareable, SvREFCNT_inc_NN (sv));
919 949
920 SV *osv = decode_sv (dec); 950 SV *osv = decode_sv (dec);
921 sv_setsv (sv, osv); 951 sv_setsv (sv, osv);
922 SvREFCNT_dec_NN (osv); 952 SvREFCNT_dec_NN (osv);
953 }
954 else
955 {
956 av_push (dec->shareable, &PL_sv_undef);
957 int idx = AvFILLp (dec->shareable);
958 sv = decode_sv (dec);
959 av_store (dec->shareable, idx, SvREFCNT_inc_NN (sv));
960 }
923 } 961 }
924 break; 962 break;
925 963
926 case CBOR_TAG_VALUE_SHAREDREF: 964 case CBOR_TAG_VALUE_SHAREDREF:
927 { 965 {
932 970
933 if (!dec->shareable || (int)idx > AvFILLp (dec->shareable)) 971 if (!dec->shareable || (int)idx > AvFILLp (dec->shareable))
934 ERR ("corrupted CBOR data (sharedref index out of bounds)"); 972 ERR ("corrupted CBOR data (sharedref index out of bounds)");
935 973
936 sv = SvREFCNT_inc_NN (AvARRAY (dec->shareable)[idx]); 974 sv = SvREFCNT_inc_NN (AvARRAY (dec->shareable)[idx]);
975
976 if (sv == &PL_sv_undef)
977 ERR ("cyclic CBOR data structure found, but allow_cycles is not enabled");
937 } 978 }
938 break; 979 break;
939 980
940 case CBOR_TAG_PERL_OBJECT: 981 case CBOR_TAG_PERL_OBJECT:
941 { 982 {
956 if (!method) 997 if (!method)
957 ERR ("cannot decode perl-object (package does not have a THAW method)"); 998 ERR ("cannot decode perl-object (package does not have a THAW method)");
958 999
959 dSP; 1000 dSP;
960 1001
961 ENTER; SAVETMPS; PUSHMARK (SP); 1002 ENTER; SAVETMPS;
1003 PUSHMARK (SP);
962 EXTEND (SP, len + 1); 1004 EXTEND (SP, len + 1);
963 // we re-bless the reference to get overload and other niceties right 1005 // we re-bless the reference to get overload and other niceties right
964 PUSHs (*av_fetch (av, 0, 1)); 1006 PUSHs (*av_fetch (av, 0, 1));
965 PUSHs (sv_cbor); 1007 PUSHs (sv_cbor);
966 1008
991 default: 1033 default:
992 { 1034 {
993 sv = decode_sv (dec); 1035 sv = decode_sv (dec);
994 1036
995 dSP; 1037 dSP;
996 ENTER; SAVETMPS; PUSHMARK (SP); 1038 ENTER; SAVETMPS;
1039 SAVESTACK_POS ();
1040 PUSHMARK (SP);
997 EXTEND (SP, 2); 1041 EXTEND (SP, 2);
998 PUSHs (newSVuv (tag)); 1042 PUSHs (newSVuv (tag));
999 PUSHs (sv); 1043 PUSHs (sv);
1000 1044
1001 PUTBACK; 1045 PUTBACK;
1110 1154
1111 return newSVnv (ecb_binary64_to_double (fp)); 1155 return newSVnv (ecb_binary64_to_double (fp));
1112 } 1156 }
1113 1157
1114 // 0..19 unassigned simple 1158 // 0..19 unassigned simple
1115 // 24 reserved + unassigned (reserved values are not encodable) 1159 // 24 reserved + unassigned simple (reserved values are not encodable)
1160 // 28-30 unassigned misc
1161 // 31 break code
1116 default: 1162 default:
1117 ERR ("corrupted CBOR data (reserved/unassigned major 7 value)"); 1163 ERR ("corrupted CBOR data (reserved/unassigned/unexpected major 7 value)");
1118 } 1164 }
1119 1165
1120 break; 1166 break;
1121 } 1167 }
1122 1168
1125} 1171}
1126 1172
1127static SV * 1173static SV *
1128decode_cbor (SV *string, CBOR *cbor, char **offset_return) 1174decode_cbor (SV *string, CBOR *cbor, char **offset_return)
1129{ 1175{
1130 dec_t dec = { }; 1176 dec_t dec = { 0 };
1131 SV *sv; 1177 SV *sv;
1132 STRLEN len; 1178 STRLEN len;
1133 char *data = SvPVbyte (string, len); 1179 char *data = SvPVbyte (string, len);
1134 1180
1135 if (len > cbor->max_size && cbor->max_size) 1181 if (len > cbor->max_size && cbor->max_size)
1149 if (dec.cur != dec.end && !dec.err) 1195 if (dec.cur != dec.end && !dec.err)
1150 dec.err = "garbage after CBOR object"; 1196 dec.err = "garbage after CBOR object";
1151 1197
1152 if (dec.err) 1198 if (dec.err)
1153 { 1199 {
1200 if (dec.shareable)
1201 {
1202 // need to break cyclic links, which whould all be in shareable
1203 int i;
1204 SV **svp;
1205
1206 for (i = av_len (dec.shareable) + 1; i--; )
1207 if ((svp = av_fetch (dec.shareable, i, 0)))
1208 sv_setsv (*svp, &PL_sv_undef);
1209 }
1210
1154 SvREFCNT_dec (sv); 1211 SvREFCNT_dec (sv);
1155 croak ("%s, at offset %d (octet 0x%02x)", dec.err, dec.cur - (U8 *)data, (int)(uint8_t)*dec.cur); 1212 croak ("%s, at offset %d (octet 0x%02x)", dec.err, dec.cur - (U8 *)data, (int)(uint8_t)*dec.cur);
1156 } 1213 }
1157 1214
1158 sv = sv_2mortal (sv); 1215 sv = sv_2mortal (sv);
1159 1216
1160 return sv; 1217 return sv;
1161} 1218}
1162 1219
1220/////////////////////////////////////////////////////////////////////////////
1221// incremental parser
1222
1223#define INCR_DONE(cbor) (AvFILLp (cbor->incr_count) < 0)
1224
1225// returns 0 for notyet, 1 for success or error
1226static int
1227incr_parse (CBOR *self, SV *cborstr)
1228{
1229 STRLEN cur;
1230 SvPV (cborstr, cur);
1231
1232 while (ecb_expect_true (self->incr_need <= cur))
1233 {
1234 // table of integer count bytes
1235 static I8 incr_len[MINOR_MASK + 1] = {
1236 0, 0, 0, 0, 0, 0, 0, 0,
1237 0, 0, 0, 0, 0, 0, 0, 0,
1238 0, 0, 0, 0, 0, 0, 0, 0,
1239 1, 2, 4, 8,-1,-1,-1,-2
1240 };
1241
1242 const U8 *p = SvPVX (cborstr) + self->incr_pos;
1243 U8 m = *p & MINOR_MASK;
1244 IV count = SvIVX (AvARRAY (self->incr_count)[AvFILLp (self->incr_count)]);
1245 I8 ilen = incr_len[m];
1246
1247 self->incr_need = self->incr_pos + 1;
1248
1249 if (ecb_expect_false (ilen < 0))
1250 {
1251 if (m != MINOR_INDEF)
1252 return 1; // error
1253
1254 if (*p == (MAJOR_MISC | MINOR_INDEF))
1255 {
1256 if (count >= 0)
1257 return 1; // error
1258
1259 count = 1;
1260 }
1261 else
1262 {
1263 av_push (self->incr_count, newSViv (-1)); //TODO: nest
1264 count = -1;
1265 }
1266 }
1267 else
1268 {
1269 self->incr_need += ilen;
1270 if (ecb_expect_false (self->incr_need > cur))
1271 return 0;
1272
1273 int major = *p >> MAJOR_SHIFT;
1274
1275 switch (major)
1276 {
1277 case MAJOR_TAG >> MAJOR_SHIFT:
1278 ++count; // tags merely prefix another value
1279 break;
1280
1281 case MAJOR_BYTES >> MAJOR_SHIFT:
1282 case MAJOR_TEXT >> MAJOR_SHIFT:
1283 case MAJOR_ARRAY >> MAJOR_SHIFT:
1284 case MAJOR_MAP >> MAJOR_SHIFT:
1285 {
1286 UV len;
1287
1288 if (ecb_expect_false (ilen))
1289 {
1290 len = 0;
1291
1292 do {
1293 len = (len << 8) | *++p;
1294 } while (--ilen);
1295 }
1296 else
1297 len = m;
1298
1299 switch (major)
1300 {
1301 case MAJOR_BYTES >> MAJOR_SHIFT:
1302 case MAJOR_TEXT >> MAJOR_SHIFT:
1303 self->incr_need += len;
1304 if (ecb_expect_false (self->incr_need > cur))
1305 return 0;
1306
1307 break;
1308
1309 case MAJOR_MAP >> MAJOR_SHIFT:
1310 len <<= 1;
1311 case MAJOR_ARRAY >> MAJOR_SHIFT:
1312 if (len)
1313 {
1314 av_push (self->incr_count, newSViv (len + 1)); //TODO: nest
1315 count = len + 1;
1316 }
1317 break;
1318 }
1319 }
1320 }
1321 }
1322
1323 self->incr_pos = self->incr_need;
1324
1325 if (count > 0)
1326 {
1327 while (!--count)
1328 {
1329 if (!AvFILLp (self->incr_count))
1330 return 1; // done
1331
1332 SvREFCNT_dec_NN (av_pop (self->incr_count));
1333 count = SvIVX (AvARRAY (self->incr_count)[AvFILLp (self->incr_count)]);
1334 }
1335
1336 SvIVX (AvARRAY (self->incr_count)[AvFILLp (self->incr_count)]) = count;
1337 }
1338 }
1339
1340 return 0;
1341}
1342
1343
1163///////////////////////////////////////////////////////////////////////////// 1344/////////////////////////////////////////////////////////////////////////////
1164// XS interface functions 1345// XS interface functions
1165 1346
1166MODULE = CBOR::XS PACKAGE = CBOR::XS 1347MODULE = CBOR::XS PACKAGE = CBOR::XS
1167 1348
1207void shrink (CBOR *self, int enable = 1) 1388void shrink (CBOR *self, int enable = 1)
1208 ALIAS: 1389 ALIAS:
1209 shrink = F_SHRINK 1390 shrink = F_SHRINK
1210 allow_unknown = F_ALLOW_UNKNOWN 1391 allow_unknown = F_ALLOW_UNKNOWN
1211 allow_sharing = F_ALLOW_SHARING 1392 allow_sharing = F_ALLOW_SHARING
1393 allow_cycles = F_ALLOW_CYCLES
1212 pack_strings = F_PACK_STRINGS 1394 pack_strings = F_PACK_STRINGS
1395 validate_utf8 = F_VALIDATE_UTF8
1213 PPCODE: 1396 PPCODE:
1214{ 1397{
1215 if (enable) 1398 if (enable)
1216 self->flags |= ix; 1399 self->flags |= ix;
1217 else 1400 else
1223void get_shrink (CBOR *self) 1406void get_shrink (CBOR *self)
1224 ALIAS: 1407 ALIAS:
1225 get_shrink = F_SHRINK 1408 get_shrink = F_SHRINK
1226 get_allow_unknown = F_ALLOW_UNKNOWN 1409 get_allow_unknown = F_ALLOW_UNKNOWN
1227 get_allow_sharing = F_ALLOW_SHARING 1410 get_allow_sharing = F_ALLOW_SHARING
1411 get_allow_cycles = F_ALLOW_CYCLES
1228 get_pack_strings = F_PACK_STRINGS 1412 get_pack_strings = F_PACK_STRINGS
1413 get_validate_utf8 = F_VALIDATE_UTF8
1229 PPCODE: 1414 PPCODE:
1230 XPUSHs (boolSV (self->flags & ix)); 1415 XPUSHs (boolSV (self->flags & ix));
1231 1416
1232void max_depth (CBOR *self, U32 max_depth = 0x80000000UL) 1417void max_depth (CBOR *self, U32 max_depth = 0x80000000UL)
1233 PPCODE: 1418 PPCODE:
1282 EXTEND (SP, 2); 1467 EXTEND (SP, 2);
1283 PUSHs (sv); 1468 PUSHs (sv);
1284 PUSHs (sv_2mortal (newSVuv (offset - SvPVX (cborstr)))); 1469 PUSHs (sv_2mortal (newSVuv (offset - SvPVX (cborstr))));
1285} 1470}
1286 1471
1472void incr_parse (CBOR *self, SV *cborstr)
1473 ALIAS:
1474 incr_parse_multiple = 1
1475 PPCODE:
1476{
1477 if (SvUTF8 (cborstr))
1478 sv_utf8_downgrade (cborstr, 0);
1479
1480 if (!self->incr_count)
1481 {
1482 self->incr_count = newAV ();
1483 self->incr_pos = 0;
1484 self->incr_need = 1;
1485
1486 av_push (self->incr_count, newSViv (1));
1487 }
1488
1489 do
1490 {
1491 if (!incr_parse (self, cborstr))
1492 {
1493 if (self->incr_need > self->max_size && self->max_size)
1494 croak ("attempted decode of CBOR text of %lu bytes size, but max_size is set to %lu",
1495 (unsigned long)self->incr_need, (unsigned long)self->max_size);
1496
1497 break;
1498 }
1499
1500 SV *sv;
1501 char *offset;
1502
1503 PUTBACK; sv = decode_cbor (cborstr, self, &offset); SPAGAIN;
1504 XPUSHs (sv);
1505
1506 sv_chop (cborstr, offset);
1507
1508 av_clear (self->incr_count);
1509 av_push (self->incr_count, newSViv (1));
1510
1511 self->incr_pos = 0;
1512 self->incr_need = self->incr_pos + 1;
1513 }
1514 while (ix);
1515}
1516
1517void incr_reset (CBOR *self)
1518 CODE:
1519{
1520 SvREFCNT_dec (self->incr_count);
1521 self->incr_count = 0;
1522}
1523
1287void DESTROY (CBOR *self) 1524void DESTROY (CBOR *self)
1288 PPCODE: 1525 PPCODE:
1289 cbor_free (self); 1526 cbor_free (self);
1290 1527
1291PROTOTYPES: ENABLE 1528PROTOTYPES: ENABLE

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines