/* * Copyright (C) 2022 by sysmocom - s.f.m.c. GmbH * Author: Alexander Couzens * * This file is part of Open5GS. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "ogs-dbi.h" #include "json-private.h" #include #define MAX_DB_PROFILES 256 #define MAX_DB_APNS 10 typedef struct ogs_json_apn_profile_s { int32_t id; ogs_qos_t qos; bool valid; } ogs_json_apn_profile_t; typedef struct ogs_json_apn_s { char *apn; ogs_json_apn_profile_t profiles[MAX_DB_PROFILES]; ogs_bitrate_t ambr; /* APN-AMBR */ bool valid; } ogs_json_apn_t; ogs_json_apn_t g_apns[MAX_DB_APNS]; static ogs_json_apn_profile_t *ogs_dbi_static_alloc_db_entry(ogs_json_apn_t *db_apn) { int i = 0; ogs_assert(db_apn); ogs_json_apn_profile_t *entry; for (i = 0; i < MAX_DB_PROFILES; i++) { entry = &db_apn->profiles[i]; if (entry->valid) continue; return entry; } return NULL; } static ogs_json_apn_profile_t *ogs_dbi_static_get_apn_profile(ogs_json_apn_t *db_apn, int profile_id) { int i = 0; ogs_assert(db_apn); for (i = 0; i < MAX_DB_PROFILES; i++) { ogs_json_apn_profile_t *entry = &db_apn->profiles[i]; if (!entry->valid) continue; if (entry->id != profile_id) continue; return entry; } return NULL; } static ogs_json_apn_t *ogs_dbi_static_get_apn(const char *apn) { int i; for (i = 0; i < MAX_DB_APNS; i++) { ogs_json_apn_t *entry = &g_apns[i]; if (!entry->valid) continue; if (!ogs_strcasecmp(entry->apn, apn)) return entry; } return NULL; } static ogs_json_apn_t *ogs_dbi_static_alloc_apn(void) { int i = 0; ogs_json_apn_t *apn; for (i = 0; i < MAX_DB_APNS; i++) { apn = &g_apns[i]; if (apn->valid) continue; return apn; } return NULL; } static int cjson_get_uint64_t(const cJSON * const top, const char *item_chr, int required, uint64_t min, uint64_t max, uint64_t *value) { uint64_t number; ogs_assert(value); cJSON *item = cJSON_GetObjectItem(top, item_chr); if (!item) { if (!required) return 0; ogs_error("Could not find mandatory field %s\n", item_chr); return -ENOENT; } if (!cJSON_IsNumber(item)) { ogs_error("Invalid value for %s. It must be a number!\n", item_chr); return -EINVAL; } number = cJSON_GetNumberValue(item); if (!(min <= number && number <= max)) { ogs_error("Invalid value for %s: %ld. It must be %ld <= x <= %ld\n", item_chr, number, min, max); return -ERANGE; } *value = number; return 0; } static int cjson_get_int(const cJSON * const top, const char *item_chr, int required, int min, int max, int *value) { int number; ogs_assert(value); cJSON *item = cJSON_GetObjectItem(top, item_chr); if (!item) { if (!required) return 0; ogs_error("Could not find mandatory field %s\n", item_chr); return -ENOENT; } if (!cJSON_IsNumber(item)) { ogs_error("Invalid value for %s. It must be a number!\n", item_chr); return -EINVAL; } number = cJSON_GetNumberValue(item); if (!(min <= number && number <= max)) { ogs_error("Invalid value for %s: %d. It must be %d <= x <= %d\n", item_chr, number, min, max); return -ERANGE; } *value = number; return 0; } static int get_bandwidth(cJSON *top, const char *mbr, ogs_bitrate_t *bitrate) { int ret; ogs_assert(mbr); ogs_assert(top); ogs_assert(bitrate); cJSON *obj = cJSON_GetObjectItem(top, mbr); if (!obj) return 0; if (!cJSON_IsObject(obj)) { ogs_error("%s has the wrong type. It is not an object!\n", mbr); return -EINVAL; } /* TODO: this will break close to 2 gbit */ ret = cjson_get_uint64_t(obj, "up", 0, 0, UINT64_MAX, &bitrate->uplink); if (ret) return ret; ret = cjson_get_uint64_t(obj, "down", 0, 0, UINT32_MAX, &bitrate->downlink); if (ret) return ret; return 0; } int ogs_dbi_json_init(const char *filepath, const char *apn) { int ret = 0; long filesize = -1; ssize_t mem_read; ogs_json_apn_t *db_apn = ogs_dbi_static_get_apn(apn); FILE *filefp; char *buf = NULL; int profile_id; ogs_dbi_select_interface("json"); if (db_apn) { return -EALREADY; } filefp = fopen(filepath, "r"); if (!filefp) { return -ENOENT; } if (fseek(filefp, 0L, SEEK_END) < 0) { ret = -ENOENT; goto fail; } filesize = ftell(filefp); if (filesize < 0) { ret = -ENOENT; goto fail; } buf = ogs_malloc(filesize); if (!buf) { ret = -ENOMEM; goto fail; } if (fseek(filefp, 0L, SEEK_SET) < 0) { ret = -ENOENT; goto fail; } mem_read = fread(buf, filesize, 1, filefp); if (mem_read != 1 && !feof(filefp)) { ret = -1; goto fail; } cJSON *profiles = cJSON_ParseWithLength(buf, filesize); if (!profiles) { const char *error_ptr = cJSON_GetErrorPtr(); if (error_ptr != NULL) { ogs_error("Error before: %s\n", error_ptr); } ret = -1; goto fail; } if (!cJSON_IsArray(profiles)) { ogs_error("Profile root object must be an array"); ret = -2; goto fail; } db_apn = ogs_dbi_static_alloc_apn(); if (!db_apn) { ogs_error("Too many APNs\n"); ret = -ENOENT; goto fail; } memset(db_apn, '\0', sizeof(*db_apn)); db_apn->apn = ogs_strdup(apn); db_apn->valid = 1; cJSON *profile; cJSON_ArrayForEach(profile, profiles) { int number; ogs_json_apn_profile_t *entry; ret = cjson_get_int(profile, "id", 1, 0, 65535, &profile_id); if (ret) goto fail; entry = ogs_dbi_static_get_apn_profile(db_apn, profile_id); if (entry) { ogs_error("Doublicated profile with id %d\n", profile_id); ret = -1; goto fail; } entry = ogs_dbi_static_alloc_db_entry(db_apn); if (!entry) { ogs_error("Tooo many db entries for apn %s\n", apn); ret = -1; goto fail; } ogs_info("Loading json profile for apn: \"%s\" / charging id \"%d\"", apn, profile_id); entry->id = profile_id; ret = cjson_get_int(profile, "qci", 1, 0, 16, &number); if (ret) goto fail; entry->qos.index = number & 0xff; ret = get_bandwidth(profile, "ambr", &entry->qos.mbr); if (ret) goto fail; ret = get_bandwidth(profile, "gbr", &entry->qos.gbr); if (ret) goto fail; /* TODO: check boundary check for fields */ ret = cjson_get_int(profile, "priority", 1, 0, 15, &number); if (ret) goto fail; entry->qos.arp.priority_level = number & 0xff; ret = cjson_get_int(profile, "pre_emption_capability", 1, 0, 1, &number); if (ret) goto fail; if (number) entry->qos.arp.pre_emption_capability = OGS_5GC_PRE_EMPTION_ENABLED; else entry->qos.arp.pre_emption_capability = OGS_5GC_PRE_EMPTION_DISABLED; ret = cjson_get_int(profile, "pre_emption_vulnerability", 1, 0, 1, &number); if (ret) goto fail; if (number) entry->qos.arp.pre_emption_vulnerability = OGS_5GC_PRE_EMPTION_ENABLED; else entry->qos.arp.pre_emption_vulnerability = OGS_5GC_PRE_EMPTION_DISABLED; entry->valid = true; } if (profiles) cJSON_Delete(profiles); if (buf) ogs_free(buf); return 0; fail: if (db_apn->apn) ogs_free(db_apn->apn); if (profiles) cJSON_Delete(profiles); if (buf) ogs_free(buf); fclose(filefp); return ret; } static int json_session_data(char *supi, ogs_s_nssai_t *s_nssai, char *dnn, ogs_session_data_t *session_data) { ogs_session_t *session = &session_data->session; ogs_json_apn_profile_t *profile; ogs_json_apn_t *db_apn = ogs_dbi_static_get_apn(dnn); int charging_char = -1; if (!db_apn) db_apn = ogs_dbi_static_get_apn("*"); if (!db_apn) { ogs_error("Couldn't find a profile for dnn (%s)", dnn); return OGS_ERROR; } profile = ogs_dbi_static_get_apn_profile(db_apn, charging_char); /* try default profile */ if (!profile && charging_char != -1) profile = ogs_dbi_static_get_apn_profile(db_apn, -1); if (!profile) { ogs_error("Couldn't find a profile for dnn (%s) with charging characteristic %d", dnn, charging_char); return OGS_ERROR; } session->name = ogs_strndup("json_profile", strlen("json_profile")); /* FIXME: the ambr shouldn't be qos.mbr, instead it should be from apn.ambr, but neither PCRF nor PCEF support it this way */ session->ambr = profile->qos.mbr; session->qos = profile->qos; return OGS_OK; } static void json_final(void) { int i; for (i = 0; i < MAX_DB_APNS; i++) { ogs_json_apn_t *db_apn = &g_apns[i]; if (!db_apn->valid) continue; db_apn->valid = 0; if (db_apn->apn) { ogs_free(db_apn->apn); db_apn->apn = NULL; } } } ogs_dbi_t ogs_dbi_json_interface = { .name = "json", /* session */ .session_data = json_session_data, .final = json_final, };