/* * Copyright (c) 2015 Jiri Svoboda * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @addtogroup libriff * @{ */ /** * @file RIFF chunk. */ #include #include #include #include #include #include #include /** Open RIFF file for writing * * @param fname File name * @param rrw Place to store pointer to RIFF writer * * @return EOK on success, ENOMEM if out of memory, EIO if failed to open * file. */ errno_t riff_wopen(const char *fname, riffw_t **rrw) { riffw_t *rw; rw = calloc(1, sizeof(riffw_t)); if (rw == NULL) return ENOMEM; rw->f = fopen(fname, "wb"); if (rw->f == NULL) { free(rw); return EIO; } *rrw = rw; return EOK; } /** Close RIFF for writing. * * @param rw RIFF writer * @return EOK on success. On write error EIO is returned and RIFF writer * is destroyed anyway. */ errno_t riff_wclose(riffw_t *rw) { int rv; rv = fclose(rw->f); free(rw); return (rv == 0) ? EOK : EIO; } /** Write uint32_t value into RIFF file * * @param rw RIFF writer * @param v Value * @return EOK on success, EIO on error. */ errno_t riff_write_uint32(riffw_t *rw, uint32_t v) { uint32_t vle; vle = host2uint32_t_le(v); if (fwrite(&vle, 1, sizeof(vle), rw->f) < sizeof(vle)) return EIO; return EOK; } /** Begin writing chunk. * * @param rw RIFF writer * @param ckid Chunk ID * @param wchunk Pointer to chunk structure to fill in * * @return EOK on success, EIO on write error */ errno_t riff_wchunk_start(riffw_t *rw, riff_ckid_t ckid, riff_wchunk_t *wchunk) { long pos; errno_t rc; pos = ftell(rw->f); if (pos < 0) return EIO; wchunk->ckstart = pos + 2 * sizeof(uint32_t); rc = riff_write_uint32(rw, ckid); if (rc != EOK) { assert(rc == EIO); return EIO; } rc = riff_write_uint32(rw, 0); if (rc != EOK) { assert(rc == EIO); return EIO; } return EOK; } /** Finish writing chunk. * * @param rw RIFF writer * @param wchunk Pointer to chunk structure * * @return EOK on success, EIO error. */ errno_t riff_wchunk_end(riffw_t *rw, riff_wchunk_t *wchunk) { long pos; long cksize; uint8_t pad; size_t nw; errno_t rc; pos = ftell(rw->f); if (pos < 0) return EIO; cksize = pos - wchunk->ckstart; if (pos % 2 != 0) { ++pos; pad = 0; nw = fwrite(&pad, 1, sizeof(pad), rw->f); if (nw != sizeof(pad)) return EIO; } if (fseek(rw->f, wchunk->ckstart - 4, SEEK_SET) < 0) return EIO; rc = riff_write_uint32(rw, cksize); if (rc != EOK) { assert(rc == EIO); return EIO; } if (fseek(rw->f, pos, SEEK_SET) < 0) return EIO; return EOK; } /** Write data into RIFF file. * * @param rw RIFF writer * @param data Pointer to data * @param bytes Number of bytes to write * * @return EOK on success, EIO on error. */ errno_t riff_write(riffw_t *rw, void *data, size_t bytes) { size_t nw; nw = fwrite(data, 1, bytes, rw->f); if (nw != bytes) return EIO; return EOK; } /** Open RIFF file for reading. * * @param fname File name * @param riffck Place to store root (RIFF) chunk * @param rrr Place to store pointer to RIFF reader * * @return EOK on success, ENOMEM if out of memory, EIO if failed to open * file.. */ errno_t riff_ropen(const char *fname, riff_rchunk_t *riffck, riffr_t **rrr) { riffr_t *rr; riff_rchunk_t fchunk; long fsize; errno_t rc; int rv; rr = calloc(1, sizeof(riffr_t)); if (rr == NULL) { rc = ENOMEM; goto error; } rr->pos = 0; rr->f = fopen(fname, "rb"); if (rr->f == NULL) { rc = EIO; goto error; } rv = fseek(rr->f, 0, SEEK_END); if (rv < 0) { rc = EIO; goto error; } fsize = ftell(rr->f); if (fsize < 0) { rc = EIO; goto error; } rv = fseek(rr->f, 0, SEEK_SET); if (rv < 0) { rc = EIO; goto error; } fchunk.riffr = rr; fchunk.ckstart = 0; fchunk.cksize = fsize; rc = riff_rchunk_start(&fchunk, riffck); if (rc != EOK) goto error; *rrr = rr; return EOK; error: if (rr != NULL && rr->f != NULL) fclose(rr->f); free(rr); return rc; } /** Close RIFF for reading. * * @param rr RIFF reader * @return EOK on success, EIO on error. */ errno_t riff_rclose(riffr_t *rr) { errno_t rc; rc = fclose(rr->f); free(rr); return rc == 0 ? EOK : EIO; } /** Read uint32_t from RIFF file. * * @param rchunk RIFF chunk * @param v Place to store value * @return EOK on success, ELIMIT if at the end of parent chunk, EIO on error. */ errno_t riff_read_uint32(riff_rchunk_t *rchunk, uint32_t *v) { uint32_t vle; errno_t rc; size_t nread; rc = riff_read(rchunk, &vle, sizeof(vle), &nread); if (rc != EOK) return rc; if (nread != sizeof(vle)) return ELIMIT; *v = uint32_t_le2host(vle); return EOK; } /** Start reading RIFF chunk. * * @param parent Parent chunk * @param rchunk Pointer to chunk structure to fill in * * @return EOK on success, ELIMIT if at the end of parent chunk, * EIO on error. */ errno_t riff_rchunk_start(riff_rchunk_t *parent, riff_rchunk_t *rchunk) { errno_t rc; rchunk->riffr = parent->riffr; rchunk->ckstart = parent->riffr->pos + 8; rc = riff_read_uint32(parent, &rchunk->ckid); if (rc != EOK) goto error; rc = riff_read_uint32(parent, &rchunk->cksize); if (rc != EOK) goto error; return EOK; error: return rc; } /** Find and start reading RIFF chunk of with specific chunk ID. * Other types of chunks are skipped. * * @param parent Parent chunk * @param rchunk Pointer to chunk structure to fill in * * @return EOK on success, ENOENT chunk was not found and end was reached * EIO on error. */ errno_t riff_rchunk_match(riff_rchunk_t *parent, riff_ckid_t ckid, riff_rchunk_t *rchunk) { errno_t rc; while (true) { rc = riff_rchunk_start(parent, rchunk); if (rc == ELIMIT) return ENOENT; if (rc != EOK) return rc; if (rchunk->ckid == ckid) break; rc = riff_rchunk_end(rchunk); if (rc != EOK) return rc; } return EOK; } /** Find and start reading RIFF LIST chunk of specified type. * Other chunks or LIST chunks of other type are skipped. * * @param parent Parent chunk * @param rchunk Pointer to chunk structure to fill in * * @return EOK on success, ENOENT chunk was not found and end was reached * EIO on error. */ errno_t riff_rchunk_list_match(riff_rchunk_t *parent, riff_ltype_t ltype, riff_rchunk_t *rchunk) { errno_t rc; riff_ltype_t rltype; while (true) { rc = riff_rchunk_match(parent, CKID_LIST, rchunk); if (rc == ELIMIT) return ENOENT; if (rc != EOK) return rc; rc = riff_read_uint32(parent, &rltype); if (rc != EOK) return rc; if (rltype == ltype) break; rc = riff_rchunk_end(rchunk); if (rc != EOK) return rc; } return EOK; } /** Seek to position in chunk. * * @param rchunk RIFF chunk * @param offset Offset * @param whence SEEK_SET, SEEK_CUR or SEEK_END * @return EOK on success or an error code */ errno_t riff_rchunk_seek(riff_rchunk_t *rchunk, long offset, int whence) { long dest; int rv; switch (whence) { case SEEK_SET: dest = rchunk->ckstart + offset; break; case SEEK_END: dest = rchunk->ckstart + rchunk->cksize + offset; break; case SEEK_CUR: dest = rchunk->riffr->pos + offset; break; default: return EINVAL; } if (dest < rchunk->ckstart || (unsigned long) dest > (unsigned long) rchunk->ckstart + rchunk->cksize) { return ELIMIT; } rv = fseek(rchunk->riffr->f, dest, SEEK_SET); if (rv < 0) return EIO; rchunk->riffr->pos = dest; return EOK; } /** Return chunk data size. * * @param rchunk RIFF chunk * @return Pure data size (excluding type+size header) in bytes */ uint32_t riff_rchunk_size(riff_rchunk_t *rchunk) { return rchunk->cksize; } /** Return file offset where chunk ends * * @param rchunk RIFF chunk * @return File offset just after last data byte of the chunk */ static long riff_rchunk_get_end(riff_rchunk_t *rchunk) { return rchunk->ckstart + rchunk->cksize; } /** Return file offset of first (non-padding) byte after end of chunk. * * @param rchunk RIFF chunk * @return File offset of first non-padding byte after end of chunk */ static long riff_rchunk_get_ndpos(riff_rchunk_t *rchunk) { long ckend; ckend = riff_rchunk_get_end(rchunk); if ((ckend % 2) != 0) return ckend + 1; else return ckend; } /** Finish reading RIFF chunk. * * Seek to the first byte after end of chunk. It is allowed, though, * to return to the chunk later, e.g. using riff_rchunk_seek(@a rchunk, ..). * * @param rchunk Chunk structure * @return EOK on success, EIO on error. */ errno_t riff_rchunk_end(riff_rchunk_t *rchunk) { long ckend; uint8_t byte; size_t nread; ckend = riff_rchunk_get_ndpos(rchunk); if (rchunk->riffr->pos < ckend && ckend <= rchunk->riffr->pos + 512) { /* (Buffered) reading is faster than seeking */ while (rchunk->riffr->pos < ckend) { nread = fread(&byte, 1, sizeof(byte), rchunk->riffr->f); if (nread != sizeof(byte)) return EIO; rchunk->riffr->pos += sizeof(byte); } } else if (rchunk->riffr->pos != ckend) { /* Need to seek (backwards or too far) */ if (fseek(rchunk->riffr->f, ckend, SEEK_SET) < 0) return EIO; rchunk->riffr->pos = ckend; } return EOK; } /** Read data from RIFF chunk. * * Attempt to read @a bytes bytes from the chunk. If there is less data * left until the end of the chunk, less will be read. The actual number * of bytes read is returned in @a *nbytes (can even be 0). * * @param rchunk RIFF chunk for reading * @param buf Buffer to read to * @param bytes Number of bytes to read * @param nread Place to store number of bytes actually read * * @return EOK on success, ELIMIT if file position is not within @a rchunk, * EIO on I/O error. */ errno_t riff_read(riff_rchunk_t *rchunk, void *buf, size_t bytes, size_t *nread) { long pos; long ckend; long toread; pos = rchunk->riffr->pos; ckend = riff_rchunk_get_end(rchunk); if (pos < rchunk->ckstart || pos > ckend) return ELIMIT; toread = min(bytes, (size_t)(ckend - pos)); if (toread == 0) { *nread = 0; return EOK; } *nread = fread(buf, 1, toread, rchunk->riffr->f); if (*nread == 0) return EIO; rchunk->riffr->pos += *nread; return EOK; } /** @} */