python/src/geomserde.c (647 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include "geomserde.h" #include <math.h> #include <stdlib.h> #include <string.h> #include "geom_buf.h" #include "geos_c_dyn.h" static SedonaErrorCode sedona_serialize_point(GEOSContextHandle_t handle, const GEOSGeometry *geom, int srid, CoordinateSequenceInfo *cs_info, char **p_buf, int *p_buf_size) { if (cs_info->total_bytes == 0) { RETURN_BUFFER_FOR_EMPTY_GEOM(POINT, cs_info->coord_type, srid); } const GEOSCoordSequence *coord_seq = dyn_GEOSGeom_getCoordSeq_r(handle, geom); if (coord_seq == NULL) { return SEDONA_GEOS_ERROR; } GeomBuffer geom_buf; SedonaErrorCode err = geom_buf_alloc(&geom_buf, POINT, srid, cs_info, 0); if (err != SEDONA_SUCCESS) { return err; } err = geom_buf_write_coords(&geom_buf, handle, coord_seq, cs_info); if (err != SEDONA_SUCCESS) { goto handle_error; } *p_buf = geom_buf.buf; *p_buf_size = geom_buf.buf_size; return SEDONA_SUCCESS; handle_error: free(geom_buf.buf); return SEDONA_GEOS_ERROR; } static SedonaErrorCode sedona_deserialize_point(GEOSContextHandle_t handle, int srid, GeomBuffer *geom_buf, CoordinateSequenceInfo *cs_info, GEOSGeometry **p_geom) { GEOSGeometry *geom = NULL; if (cs_info->num_coords == 0) { geom = dyn_GEOSGeom_createEmptyPoint_r(handle); } else if (cs_info->dims == 2) { /* fast path for 2D points */ double x = *geom_buf->buf_coord++; double y = *geom_buf->buf_coord++; geom = dyn_GEOSGeom_createPointFromXY_r(handle, x, y); } else { GEOSCoordSequence *coord_seq = NULL; SedonaErrorCode err = geom_buf_read_coords(geom_buf, handle, cs_info, &coord_seq); if (err != SEDONA_SUCCESS) { return err; } geom = dyn_GEOSGeom_createPoint_r(handle, coord_seq); if (geom == NULL) { dyn_GEOSCoordSeq_destroy_r(handle, coord_seq); return SEDONA_GEOS_ERROR; } } *p_geom = geom; return SEDONA_SUCCESS; } static SedonaErrorCode sedona_serialize_linestring( GEOSContextHandle_t handle, const GEOSGeometry *geom, int srid, CoordinateSequenceInfo *cs_info, char **p_buf, int *p_buf_size) { if (cs_info->num_coords == 0) { RETURN_BUFFER_FOR_EMPTY_GEOM(LINESTRING, cs_info->coord_type, srid); } const GEOSCoordSequence *coord_seq = dyn_GEOSGeom_getCoordSeq_r(handle, geom); if (coord_seq == NULL) { return SEDONA_GEOS_ERROR; } GeomBuffer geom_buf; SedonaErrorCode err = geom_buf_alloc(&geom_buf, LINESTRING, srid, cs_info, 0); if (err != SEDONA_SUCCESS) { return err; } err = geom_buf_write_coords(&geom_buf, handle, coord_seq, cs_info); if (err != SEDONA_SUCCESS) { free(geom_buf.buf); return err; } *p_buf = geom_buf.buf; *p_buf_size = geom_buf.buf_size; return SEDONA_SUCCESS; } static SedonaErrorCode sedona_deserialize_linestring( GEOSContextHandle_t handle, int srid, GeomBuffer *geom_buf, CoordinateSequenceInfo *cs_info, GEOSGeometry **p_geom) { if (cs_info->num_coords == 0) { GEOSGeometry *geom = dyn_GEOSGeom_createEmptyLineString_r(handle); if (geom == NULL) { return SEDONA_GEOS_ERROR; } *p_geom = geom; return SEDONA_SUCCESS; } GEOSCoordSequence *coord_seq = NULL; SedonaErrorCode err = geom_buf_read_coords(geom_buf, handle, cs_info, &coord_seq); if (err != SEDONA_SUCCESS) { return err; } GEOSGeometry *geom = dyn_GEOSGeom_createLineString_r(handle, coord_seq); if (geom == NULL) { dyn_GEOSCoordSeq_destroy_r(handle, coord_seq); return SEDONA_GEOS_ERROR; } *p_geom = geom; return SEDONA_SUCCESS; } static SedonaErrorCode sedona_serialize_polygon(GEOSContextHandle_t handle, const GEOSGeometry *geom, int srid, CoordinateSequenceInfo *cs_info, char **p_buf, int *p_buf_size) { if (cs_info->num_coords == 0) { RETURN_BUFFER_FOR_EMPTY_GEOM(POLYGON, cs_info->coord_type, srid); } int num_interior_rings = dyn_GEOSGetNumInteriorRings_r(handle, geom); if (num_interior_rings == -1) { return SEDONA_GEOS_ERROR; } GeomBuffer geom_buf; int num_rings = num_interior_rings + 1; SedonaErrorCode err = geom_buf_alloc(&geom_buf, POLYGON, srid, cs_info, num_rings + 1); if (err != SEDONA_SUCCESS) { return err; } err = geom_buf_write_polygon(&geom_buf, handle, geom, cs_info); if (err != SEDONA_SUCCESS) { free(geom_buf.buf); return err; } *p_buf = geom_buf.buf; *p_buf_size = geom_buf.buf_size; return SEDONA_SUCCESS; } static SedonaErrorCode sedona_deserialize_polygon( GEOSContextHandle_t handle, int srid, GeomBuffer *geom_buf, CoordinateSequenceInfo *cs_info, GEOSGeometry **p_geom) { if (cs_info->num_coords == 0) { GEOSGeometry *geom = dyn_GEOSGeom_createEmptyPolygon_r(handle); if (geom == NULL) { return SEDONA_GEOS_ERROR; } *p_geom = geom; return SEDONA_SUCCESS; } return geom_buf_read_polygon(geom_buf, handle, cs_info, p_geom); } static SedonaErrorCode sedona_serialize_multipoint( GEOSContextHandle_t handle, const GEOSGeometry *geom, int srid, CoordinateSequenceInfo *cs_info, char **p_buf, int *p_buf_size) { int num_points = dyn_GEOSGetNumGeometries_r(handle, geom); if (num_points == -1) { return SEDONA_GEOS_ERROR; } /* cs_info->num_coords will be smaller than actual number of serialized * coordinates when there're empty points in the multipoint, so let's fix * it. */ cs_info->num_coords = num_points; GeomBuffer geom_buf; SedonaErrorCode err = geom_buf_alloc(&geom_buf, MULTIPOINT, srid, cs_info, 0); if (err != SEDONA_SUCCESS) { return err; } for (int k = 0; k < num_points; k++) { const GEOSGeometry *point = dyn_GEOSGetGeometryN_r(handle, geom, k); if (point == NULL) { err = SEDONA_GEOS_ERROR; goto handle_error; } const GEOSCoordSequence *coord_seq = dyn_GEOSGeom_getCoordSeq_r(handle, point); if (coord_seq == NULL) { err = SEDONA_GEOS_ERROR; goto handle_error; } /* check if this point is empty */ unsigned int num_coords = 0; if (dyn_GEOSCoordSeq_getSize_r(handle, coord_seq, &num_coords) == 0) { err = SEDONA_GEOS_ERROR; goto handle_error; } if (num_coords == 1) { /* non-empty point */ cs_info->num_coords = 1; err = geom_buf_write_coords(&geom_buf, handle, coord_seq, cs_info); if (err != SEDONA_SUCCESS) { goto handle_error; } } else { /* point is empty, we have to manually write NaNs */ *geom_buf.buf_coord++ = NAN; *geom_buf.buf_coord++ = NAN; if (cs_info->has_z) { *geom_buf.buf_coord++ = NAN; } if (cs_info->has_m) { *geom_buf.buf_coord++ = NAN; } } } *p_buf = geom_buf.buf; *p_buf_size = geom_buf.buf_size; return SEDONA_SUCCESS; handle_error: free(geom_buf.buf); return err; } static SedonaErrorCode sedona_deserialize_multipoint( GEOSContextHandle_t handle, int srid, GeomBuffer *geom_buf, CoordinateSequenceInfo *cs_info, GEOSGeometry **p_geom) { int num_points = cs_info->num_coords; GEOSGeometry **points = calloc(num_points, sizeof(GEOSGeometry *)); if (points == NULL) { return SEDONA_ALLOC_ERROR; } SedonaErrorCode err = SEDONA_SUCCESS; for (int k = 0; k < num_points; k++) { GEOSGeometry *point = NULL; if (cs_info->dims == 2) { /* fast path for 2D points. We can get rid of constructing a coordinate * sequence object explicitly */ double x = *geom_buf->buf_coord++; double y = *geom_buf->buf_coord++; /* x and y will be NaN when serialized point was an empty point. GEOS * will treat point with Nan ordinates as an empty point so we don't need * to handle NaN specially. */ point = dyn_GEOSGeom_createPointFromXY_r(handle, x, y); if (point == NULL) { err = SEDONA_GEOS_ERROR; goto handle_error; } } else { GEOSCoordSequence *coord_seq = NULL; cs_info->num_coords = 1; err = geom_buf_read_coords(geom_buf, handle, cs_info, &coord_seq); if (err != SEDONA_SUCCESS) { goto handle_error; } point = dyn_GEOSGeom_createPoint_r(handle, coord_seq); if (point == NULL) { dyn_GEOSCoordSeq_destroy_r(handle, coord_seq); err = SEDONA_GEOS_ERROR; goto handle_error; } } points[k] = point; } GEOSGeometry *geom = dyn_GEOSGeom_createCollection_r(handle, GEOS_MULTIPOINT, points, num_points); if (geom == NULL) { err = SEDONA_GEOS_ERROR; goto handle_error; } free(points); *p_geom = geom; return SEDONA_SUCCESS; handle_error: destroy_geometry_array(handle, points, num_points); return err; } static SedonaErrorCode sedona_serialize_multilinestring( GEOSContextHandle_t handle, const GEOSGeometry *geom, int srid, CoordinateSequenceInfo *cs_info, char **p_buf, int *p_buf_size) { int num_geoms = dyn_GEOSGetNumGeometries_r(handle, geom); if (num_geoms == -1) { return SEDONA_GEOS_ERROR; } GeomBuffer geom_buf; SedonaErrorCode err = geom_buf_alloc(&geom_buf, MULTILINESTRING, srid, cs_info, num_geoms + 1); if (err != SEDONA_SUCCESS) { return err; } if ((err = geom_buf_write_int(&geom_buf, num_geoms)) != SEDONA_SUCCESS) { return err; } for (int k = 0; k < num_geoms; k++) { const GEOSGeometry *linestring = dyn_GEOSGetGeometryN_r(handle, geom, k); if (linestring == NULL) { err = SEDONA_GEOS_ERROR; goto handle_error; } err = geom_buf_write_linear_segment(&geom_buf, handle, linestring, cs_info); if (err != SEDONA_SUCCESS) { goto handle_error; } } *p_buf = geom_buf.buf; *p_buf_size = geom_buf.buf_size; return SEDONA_SUCCESS; handle_error: free(geom_buf.buf); return err; } static SedonaErrorCode sedona_deserialize_multilinestring( GEOSContextHandle_t handle, int srid, GeomBuffer *geom_buf, CoordinateSequenceInfo *cs_info, GEOSGeometry **p_geom) { int num_geoms = 0; SedonaErrorCode err = geom_buf_read_bounded_int(geom_buf, &num_geoms); if (err != SEDONA_SUCCESS) { return err; } GEOSGeometry **linestrings = calloc(num_geoms, sizeof(GEOSGeometry *)); for (int k = 0; k < num_geoms; k++) { GEOSGeometry *linestring = NULL; if ((err = geom_buf_read_linear_segment(geom_buf, handle, cs_info, GEOS_LINESTRING, &linestring)) != SEDONA_SUCCESS) { goto handle_error; } linestrings[k] = linestring; } GEOSGeometry *geom = dyn_GEOSGeom_createCollection_r( handle, GEOS_MULTILINESTRING, linestrings, num_geoms); if (geom == NULL) { err = SEDONA_GEOS_ERROR; goto handle_error; } free(linestrings); *p_geom = geom; return SEDONA_SUCCESS; handle_error: destroy_geometry_array(handle, linestrings, num_geoms); return err; } static SedonaErrorCode sedona_serialize_multipolygon( GEOSContextHandle_t handle, const GEOSGeometry *geom, int srid, CoordinateSequenceInfo *cs_info, char **p_buf, int *p_buf_size) { int num_geoms = dyn_GEOSGetNumGeometries_r(handle, geom); if (num_geoms == -1) { return SEDONA_GEOS_ERROR; } /* collect size of structural data */ int num_rings = 0; for (int k = 0; k < num_geoms; k++) { const GEOSGeometry *polygon = dyn_GEOSGetGeometryN_r(handle, geom, k); if (polygon == NULL) { return SEDONA_GEOS_ERROR; } int num_interior_rings = dyn_GEOSGetNumInteriorRings_r(handle, polygon); if (num_interior_rings == -1) { return SEDONA_GEOS_ERROR; } if (num_interior_rings > 0) { num_rings += (num_interior_rings + 1); } else { /* check if polygon is empty */ char is_empty = dyn_GEOSisEmpty_r(handle, polygon); if (is_empty == 2) { return SEDONA_GEOS_ERROR; } num_rings += (is_empty == 1 ? 0 : 1); } } GeomBuffer geom_buf; SedonaErrorCode err = geom_buf_alloc(&geom_buf, MULTIPOLYGON, srid, cs_info, 1 + num_geoms + num_rings); if (err != SEDONA_SUCCESS) { return err; } if ((err = geom_buf_write_int(&geom_buf, num_geoms)) != SEDONA_SUCCESS) { return err; } for (int k = 0; k < num_geoms; k++) { const GEOSGeometry *polygon = dyn_GEOSGetGeometryN_r(handle, geom, k); if (polygon == NULL) { err = SEDONA_GEOS_ERROR; goto handle_error; } if ((err = geom_buf_write_polygon(&geom_buf, handle, polygon, cs_info)) != SEDONA_SUCCESS) { goto handle_error; } } *p_buf = geom_buf.buf; *p_buf_size = geom_buf.buf_size; return SEDONA_SUCCESS; handle_error: free(geom_buf.buf); return err; } static SedonaErrorCode sedona_deserialize_multipolygon( GEOSContextHandle_t handle, int srid, GeomBuffer *geom_buf, CoordinateSequenceInfo *cs_info, GEOSGeometry **p_geom) { int num_geoms = 0; SedonaErrorCode err = geom_buf_read_bounded_int(geom_buf, &num_geoms); if (err != SEDONA_SUCCESS) { return err; } GEOSGeometry **polygons = calloc(num_geoms, sizeof(GEOSGeometry *)); for (int k = 0; k < num_geoms; k++) { GEOSGeometry *polygon = NULL; if ((err = geom_buf_read_polygon(geom_buf, handle, cs_info, &polygon)) != SEDONA_SUCCESS) { goto handle_error; } polygons[k] = polygon; } GEOSGeometry *geom = dyn_GEOSGeom_createCollection_r(handle, MULTIPOLYGON, polygons, num_geoms); if (geom == NULL) { err = SEDONA_GEOS_ERROR; goto handle_error; } free(polygons); *p_geom = geom; return SEDONA_SUCCESS; handle_error: destroy_geometry_array(handle, polygons, num_geoms); return err; } static inline int aligned_offset(int offset) { return (offset + 7) & ~7; } static SedonaErrorCode sedona_serialize_geometrycollection( GEOSContextHandle_t handle, const GEOSGeometry *geom, int srid, char **p_buf, int *p_buf_size) { int num_geoms = dyn_GEOSGetNumGeometries_r(handle, geom); if (num_geoms == -1) { return SEDONA_GEOS_ERROR; } int total_size = 8; unsigned char *scratch = calloc(num_geoms, sizeof(char *) + sizeof(int)); if (scratch == NULL) { return SEDONA_ALLOC_ERROR; } char **geom_bufs = (char **)scratch; int *geom_buf_sizes = (int *)(scratch + num_geoms * sizeof(char *)); SedonaErrorCode err = SEDONA_SUCCESS; /* Serialize geometries individually */ for (int k = 0; k < num_geoms; k++) { const GEOSGeometry *child_geom = dyn_GEOSGetGeometryN_r(handle, geom, k); if (child_geom == NULL) { err = SEDONA_GEOS_ERROR; goto handle_error; } char *buf = NULL; int buf_size = 0; err = sedona_serialize_geom(handle, child_geom, &buf, &buf_size); if (err != SEDONA_SUCCESS) { goto handle_error; } geom_bufs[k] = buf; geom_buf_sizes[k] = buf_size; total_size += aligned_offset(buf_size); } char *buf = alloc_buffer_for_geom(GEOMETRYCOLLECTION, XY, srid, total_size, num_geoms); if (buf == NULL) { err = SEDONA_ALLOC_ERROR; goto handle_error; } char *p_next_geom = buf + 8; for (int k = 0; k < num_geoms; k++) { int buf_size = geom_buf_sizes[k]; memcpy(p_next_geom, geom_bufs[k], buf_size); free(geom_bufs[k]); int padding_size = aligned_offset(buf_size) - buf_size; p_next_geom += buf_size; if (padding_size > 0) { memset(p_next_geom, 0, padding_size); p_next_geom += padding_size; } } free(scratch); *p_buf = buf; *p_buf_size = total_size; return SEDONA_SUCCESS; handle_error: for (int k = 0; k < num_geoms; k++) { free(geom_bufs[k]); } free(scratch); return err; } static SedonaErrorCode deserialize_geom_buf(GEOSContextHandle_t handle, GeometryTypeId geom_type_id, int srid, GeomBuffer *geom_buf, CoordinateSequenceInfo *cs_info, GEOSGeometry **p_geom); static SedonaErrorCode sedona_deserialize_geometrycollection( GEOSContextHandle_t handle, int srid, GeomBuffer *geom_buf, CoordinateSequenceInfo *cs_info, GEOSGeometry **p_geom) { SedonaErrorCode err = SEDONA_SUCCESS; int num_geoms = cs_info->num_coords; GEOSGeometry **child_geoms = calloc(num_geoms, sizeof(GEOSGeometry *)); if (child_geoms == NULL) { return SEDONA_ALLOC_ERROR; } const char *buf = (const char *)geom_buf->buf + 8; int remaining_size = geom_buf->buf_size - 8; for (int k = 0; k < num_geoms; k++) { GEOSGeometry *child_geom = NULL; int bytes_read = 0; err = sedona_deserialize_geom(handle, buf, remaining_size, &child_geom, &bytes_read); if (err != SEDONA_SUCCESS) { goto handle_error; } child_geoms[k] = child_geom; bytes_read = aligned_offset(bytes_read); if (remaining_size < bytes_read) { err = SEDONA_INCOMPLETE_BUFFER; goto handle_error; } remaining_size -= bytes_read; buf += bytes_read; } GEOSGeometry *geom_collection = dyn_GEOSGeom_createCollection_r( handle, GEOS_GEOMETRYCOLLECTION, child_geoms, num_geoms); if (geom_collection == NULL) { err = SEDONA_GEOS_ERROR; goto handle_error; } free(child_geoms); *p_geom = geom_collection; /* set geom_buf.buf_int to mark the end of the buffer for this geometry * collection */ geom_buf->buf_int = (int *)buf; return SEDONA_SUCCESS; handle_error: destroy_geometry_array(handle, child_geoms, num_geoms); return err; } SedonaErrorCode sedona_serialize_geom(GEOSContextHandle_t handle, const GEOSGeometry *geom, char **p_buf, int *p_buf_size) { int srid = dyn_GEOSGetSRID_r(handle, geom); int geom_type_id = dyn_GEOSGeomTypeId_r(handle, geom); if (geom_type_id == GEOS_GEOMETRYCOLLECTION) { return sedona_serialize_geometrycollection(handle, geom, srid, p_buf, p_buf_size); } CoordinateSequenceInfo cs_info; SedonaErrorCode err = get_coord_seq_info_from_geom(handle, geom, &cs_info); if (err != SEDONA_SUCCESS) { return err; } switch (geom_type_id) { case GEOS_POINT: err = sedona_serialize_point(handle, geom, srid, &cs_info, p_buf, p_buf_size); break; case GEOS_LINESTRING: err = sedona_serialize_linestring(handle, geom, srid, &cs_info, p_buf, p_buf_size); break; case GEOS_LINEARRING: err = SEDONA_UNSUPPORTED_GEOM_TYPE; break; case GEOS_POLYGON: err = sedona_serialize_polygon(handle, geom, srid, &cs_info, p_buf, p_buf_size); break; case GEOS_MULTIPOINT: err = sedona_serialize_multipoint(handle, geom, srid, &cs_info, p_buf, p_buf_size); break; case GEOS_MULTILINESTRING: err = sedona_serialize_multilinestring(handle, geom, srid, &cs_info, p_buf, p_buf_size); break; case GEOS_MULTIPOLYGON: err = sedona_serialize_multipolygon(handle, geom, srid, &cs_info, p_buf, p_buf_size); break; default: err = SEDONA_UNKNOWN_GEOM_TYPE; } return err; } static SedonaErrorCode deserialize_geom_buf(GEOSContextHandle_t handle, GeometryTypeId geom_type_id, int srid, GeomBuffer *geom_buf, CoordinateSequenceInfo *cs_info, GEOSGeometry **p_geom) { SedonaErrorCode err = SEDONA_SUCCESS; switch (geom_type_id) { case POINT: err = sedona_deserialize_point(handle, srid, geom_buf, cs_info, p_geom); break; case LINESTRING: err = sedona_deserialize_linestring(handle, srid, geom_buf, cs_info, p_geom); break; case POLYGON: err = sedona_deserialize_polygon(handle, srid, geom_buf, cs_info, p_geom); break; case MULTIPOINT: err = sedona_deserialize_multipoint(handle, srid, geom_buf, cs_info, p_geom); break; case MULTILINESTRING: err = sedona_deserialize_multilinestring(handle, srid, geom_buf, cs_info, p_geom); break; case MULTIPOLYGON: err = sedona_deserialize_multipolygon(handle, srid, geom_buf, cs_info, p_geom); break; case GEOMETRYCOLLECTION: err = sedona_deserialize_geometrycollection(handle, srid, geom_buf, cs_info, p_geom); break; default: return SEDONA_UNSUPPORTED_GEOM_TYPE; } if (err != SEDONA_SUCCESS) { return err; } if (srid != 0) { dyn_GEOSSetSRID_r(handle, *p_geom, srid); } return SEDONA_SUCCESS; } SedonaErrorCode sedona_deserialize_geom(GEOSContextHandle_t handle, const char *buf, int buf_size, GEOSGeometry **p_geom, int *p_bytes_read) { GeomBuffer geom_buf; CoordinateSequenceInfo cs_info; GeometryTypeId geom_type_id; int srid; SedonaErrorCode err = read_geom_buf_header(buf, buf_size, &geom_buf, &cs_info, &geom_type_id, &srid); if (err != SEDONA_SUCCESS) { return err; } err = deserialize_geom_buf(handle, geom_type_id, srid, &geom_buf, &cs_info, p_geom); if (err != SEDONA_SUCCESS) { return err; } *p_bytes_read = (int)((unsigned char *)geom_buf.buf_int - (unsigned char *)geom_buf.buf); return SEDONA_SUCCESS; } const char *sedona_get_error_message(int err) { switch (err) { case SEDONA_SUCCESS: return ""; case SEDONA_UNKNOWN_GEOM_TYPE: return "Unknown geometry type"; case SEDONA_UNKNOWN_COORD_TYPE: return "Unknown coordinate type"; case SEDONA_UNSUPPORTED_GEOM_TYPE: return "Unsupported geometry type"; case SEDONA_INCOMPLETE_BUFFER: return "Buffer to be deserialized is incomplete"; case SEDONA_BAD_GEOM_BUFFER: return "Bad serialized geometry buffer"; case SEDONA_GEOS_ERROR: return "GEOS error"; case SEDONA_ALLOC_ERROR: return "Out of memory"; case SEDONA_INTERNAL_ERROR: return "Internal error"; default: return "Unknown failure occurred"; } }