aboutsummaryrefslogtreecommitdiffstats
path: root/src/adb_comp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/adb_comp.c')
-rw-r--r--src/adb_comp.c126
1 files changed, 114 insertions, 12 deletions
diff --git a/src/adb_comp.c b/src/adb_comp.c
index 26fb50f..2f92924 100644
--- a/src/adb_comp.c
+++ b/src/adb_comp.c
@@ -9,40 +9,142 @@
#include "apk_defines.h"
#include "adb.h"
-struct apk_istream *adb_decompress(struct apk_istream *is, adb_comp_t *compression)
+struct compression_info {
+ const char *name;
+ uint8_t min_level, max_level;
+ struct apk_ostream *(*compress)(struct apk_ostream *, uint8_t);
+ struct apk_istream *(*decompress)(struct apk_istream *);
+};
+
+static const struct compression_info compression_infos[] = {
+ [ADB_COMP_NONE] = {
+ .name = "none",
+ },
+ [ADB_COMP_DEFLATE] = {
+ .name = "deflate",
+ .compress = apk_ostream_deflate,
+ .decompress = apk_istream_deflate,
+ .min_level = 0, .max_level = 9,
+ },
+#ifdef HAVE_ZSTD
+ [ADB_COMP_ZSTD] = {
+ .name = "zstd",
+ .compress = apk_ostream_zstd,
+ .decompress = apk_istream_zstd,
+ .min_level = 0, .max_level = 22,
+ },
+#endif
+};
+
+static const struct compression_info *compression_info_by_name(const char *name, size_t n, uint8_t *compalg)
+{
+ for (int i = 0; i < ARRAY_SIZE(compression_infos); i++) {
+ const struct compression_info *ci = &compression_infos[i];
+ if (strlen(ci->name) == n && strncmp(name, ci->name, n) == 0) {
+ *compalg = i;
+ return ci;
+ }
+ }
+ return NULL;
+}
+
+static const struct compression_info *compression_info_by_alg(uint8_t alg)
+{
+ if (alg >= ARRAY_SIZE(compression_infos)) return NULL;
+ return &compression_infos[alg];
+}
+
+int adb_parse_compression(const char *spec_string, struct adb_compression_spec *spec)
+{
+ const struct compression_info *ci;
+ const char *delim = strchrnul(spec_string, ':');
+ char *end;
+ long level = 0;
+
+ ci = compression_info_by_name(spec_string, delim - spec_string, &spec->alg);
+ if (!ci) goto err;
+ if (*delim != 0) {
+ if (delim[1] == 0) goto err;
+ if (ci->max_level == 0) goto err;
+
+ level = strtol(delim+1, &end, 0);
+ if (*end != 0) goto err;
+ if (level < ci->min_level || level > ci->max_level) goto err;
+ }
+ if (spec->alg == ADB_COMP_NONE) level = 1;
+ spec->level = level;
+ return 0;
+err:
+ *spec = (struct adb_compression_spec) { .alg = ADB_COMP_NONE };
+ return -APKE_ADB_COMPRESSION;
+}
+
+struct apk_istream *adb_decompress(struct apk_istream *is, struct adb_compression_spec *retspec)
{
- adb_comp_t c = -1;
+ struct adb_compression_spec spec = { .alg = ADB_COMP_NONE };
- if (IS_ERR_OR_NULL(is)) return is;
+ if (IS_ERR(is)) return is;
uint8_t *buf = apk_istream_peek(is, 4);
if (IS_ERR(buf)) return ERR_PTR(apk_istream_close_error(is, PTR_ERR(buf)));
if (memcmp(buf, "ADB", 3) != 0) return ERR_PTR(apk_istream_close_error(is, -APKE_ADB_HEADER));
switch (buf[3]) {
case '.':
- c = ADB_COMP_NONE;
+ spec.alg = ADB_COMP_NONE;
+ spec.level = 1;
break;
case 'd':
- c = ADB_COMP_DEFLATE;
apk_istream_get(is, 4);
- is = apk_istream_deflate(is);
+ spec.alg = ADB_COMP_DEFLATE;
+ break;
+ case 'c':
+ apk_istream_get(is, 4);
+ apk_istream_read(is, &spec, sizeof spec);
break;
+ default:
+ goto err;
}
- if (c == -1) return ERR_PTR(apk_istream_close_error(is, -APKE_ADB_COMPRESSION));
- if (compression) *compression = c;
+
+ const struct compression_info *ci = compression_info_by_alg(spec.alg);
+ if (!ci) goto err;
+
+ if (spec.alg != ADB_COMP_NONE)
+ is = ci->decompress(is);
+
+ if (retspec) *retspec = spec;
+
return is;
+err:
+ return ERR_PTR(apk_istream_close_error(is, -APKE_ADB_COMPRESSION));
}
-struct apk_ostream *adb_compress(struct apk_ostream *os, adb_comp_t compression)
+struct apk_ostream *adb_compress(struct apk_ostream *os, struct adb_compression_spec *spec)
{
- if (IS_ERR_OR_NULL(os)) return os;
- switch (compression) {
+ const struct compression_info *ci;
+
+ if (IS_ERR(os)) return os;
+ if (spec->alg == ADB_COMP_NONE && spec->level == 0) {
+ *spec = (struct adb_compression_spec) {
+ .alg = ADB_COMP_DEFLATE,
+ };
+ }
+ ci = compression_info_by_alg(spec->alg);
+ if (!ci) goto err;
+ if (spec->level < ci->min_level || spec->level > ci->max_level) goto err;
+
+ switch (spec->alg) {
case ADB_COMP_NONE:
return os;
case ADB_COMP_DEFLATE:
+ if (spec->level != 0) break;
if (apk_ostream_write(os, "ADBd", 4) < 0) goto err;
- return apk_ostream_deflate(os);
+ return apk_ostream_deflate(os, 0);
}
+
+ if (apk_ostream_write(os, "ADBc", 4) < 0) goto err;
+ if (apk_ostream_write(os, spec, sizeof *spec) < 0) goto err;
+ return ci->compress(os, spec->level);
+
err:
apk_ostream_cancel(os, -APKE_ADB_COMPRESSION);
return ERR_PTR(apk_ostream_close(os));