Switch RDFF to 2-clause BSD license.
[ardour.git] / libs / ardour / rdff.c
1 /*
2   RDFF - RDF in RIFF
3   Copyright 2011 David Robillard <http://drobilla.net>
4
5   Redistribution and use in source and binary forms, with or without
6   modification, are permitted provided that the following conditions are met:
7
8   1. Redistributions of source code must retain the above copyright notice,
9      this list of conditions and the following disclaimer.
10
11   2. Redistributions in binary form must reproduce the above copyright
12      notice, this list of conditions and the following disclaimer in the
13      documentation and/or other materials provided with the distribution.
14
15   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
18   AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
19   OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24   THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27 #include <assert.h>
28 #include <errno.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32
33 #include "rdff.h"
34
35 #define CHUNK_ID_LEN 4
36
37 static const char FILE_TYPE[CHUNK_ID_LEN]  = "RDFF";  /* RDFF File ID */
38 static const char CHUNK_KVAL[CHUNK_ID_LEN] = "KVAL";  /* Key/Value Chunk ID */
39 static const char CHUNK_URID[CHUNK_ID_LEN] = "URID";  /* URI-ID Chunk ID*/
40
41 struct _RDFF {
42         FILE*    fd;
43         uint32_t size;
44         bool     write;
45 };
46
47 RDFF
48 rdff_open(const char* path, bool write)
49 {
50         FILE* fd = fopen(path, (write ? "w" : "r"));
51         if (!fd) {
52                 fprintf(stderr, "%s\n", strerror(errno));
53                 return NULL;
54         }
55
56         uint32_t size = 0;
57
58         if (write) {
59                 fwrite("RIFF", CHUNK_ID_LEN, 1, fd);    /* RIFF chunk ID */
60                 fwrite(&size, sizeof(size), 1, fd);     /* RIFF chunk size */
61                 fwrite(FILE_TYPE, CHUNK_ID_LEN, 1, fd); /* File type */
62         } else {
63                 char magic[CHUNK_ID_LEN];
64                 if (fread(magic, CHUNK_ID_LEN, 1, fd) != 1
65                     || strncmp(magic, "RIFF", CHUNK_ID_LEN)) {
66                         fclose(fd);
67                         fprintf(stderr, "%s: error: not a RIFF file\n", path);
68                         return NULL;
69                 }
70
71                 if (fread(&size, sizeof(size), 1, fd) != 1) {
72                         fclose(fd);
73                         fprintf(stderr, "%s: error: missing RIFF chunk size\n", path);
74                         return NULL;
75                 }
76
77                 if (fread(magic, CHUNK_ID_LEN, 1, fd) != 1
78                     || strncmp(magic, FILE_TYPE, CHUNK_ID_LEN)) {
79                         fclose(fd);
80                         fprintf(stderr, "%s: error: not an %s RIFF file\n",
81                                 FILE_TYPE, path);
82                         return NULL;
83                 }
84         }
85
86         RDFF ret = (RDFF)malloc(sizeof(struct _RDFF));
87         ret->fd    = fd;
88         ret->size  = size;
89         ret->write = write;
90         return ret;
91 }
92
93 #define WRITE(ptr, size, nmemb, stream) \
94         if (fwrite(ptr, size, nmemb, stream) != nmemb) { \
95                 return RDFF_STATUS_UNKNOWN_ERROR; \
96         }
97
98 RDFFStatus
99 rdff_write_uri(RDFF        file,
100                uint32_t    id,
101                const char* uri,
102                uint32_t    len)
103 {
104         const uint32_t chunk_size = sizeof(id) + len + 1;
105         WRITE(CHUNK_URID,  CHUNK_ID_LEN,       1, file->fd);
106         WRITE(&chunk_size, sizeof(chunk_size), 1, file->fd);
107         WRITE(&id,         sizeof(id),         1, file->fd);
108         WRITE(uri,         len + 1,            1, file->fd);
109         if ((chunk_size % 2)) {
110                 WRITE("", 1, 1, file->fd);  /* pad */
111         }
112         file->size += 8 + chunk_size;
113         return RDFF_STATUS_OK;
114 }
115
116 RDFFStatus
117 rdff_write_value(RDFF        file,
118                  uint32_t    key,
119                  const void* value,
120                  uint32_t    size,
121                  uint32_t    type)
122 {
123         const uint32_t chunk_size = sizeof(key) + sizeof(type) + sizeof(size) + size;
124         WRITE(CHUNK_KVAL,  CHUNK_ID_LEN,       1, file->fd);
125         WRITE(&chunk_size, sizeof(chunk_size), 1, file->fd);
126         WRITE(&key,        sizeof(key),        1, file->fd);
127         WRITE(&type,       sizeof(type),       1, file->fd);
128         WRITE(&size,       sizeof(size),       1, file->fd);
129         WRITE(value,       size,               1, file->fd);
130         if ((size % 2)) {
131                 WRITE("", 1, 1, file->fd);  /* write pad */
132         }
133         file->size += 8 + chunk_size;
134         return RDFF_STATUS_OK;
135 }
136
137 RDFFStatus
138 rdff_read_chunk(RDFF        file,
139                 RDFFChunk** buf)
140 {
141         if (feof(file->fd))
142                 return RDFF_STATUS_EOF;
143
144 #define READ(ptr, size, nmemb, stream) \
145         if (fread(ptr, size, nmemb, stream) != nmemb) { \
146                 return RDFF_STATUS_CORRUPT; \
147         }
148
149         const uint32_t alloc_size = (*buf)->size;
150
151         READ((*buf)->type,  sizeof((*buf)->type), 1, file->fd);
152         READ(&(*buf)->size, sizeof((*buf)->size), 1, file->fd);
153         if ((*buf)->size > alloc_size) {
154                 *buf = realloc(*buf, sizeof(RDFFChunk) + (*buf)->size);
155         }
156         READ((*buf)->data, (*buf)->size, 1, file->fd);
157         if (((*buf)->size % 2)) {
158                 char pad;
159                 READ(&pad, 1, 1, file->fd);  /* skip pad */
160         }
161         return RDFF_STATUS_OK;
162 }
163
164 void
165 rdff_close(RDFF file)
166 {
167         if (file) {
168                 if (file->write) {
169                         fseek(file->fd, 4, SEEK_SET);
170                         if (fwrite(&file->size, sizeof(file->size), 1, file->fd) != 1) {
171                                 fprintf(stderr, "failed to write RIFF header size\n");
172                         }
173                 }
174                 fclose(file->fd);
175         }
176
177         free(file);
178 }
179
180 #ifdef STANDALONE
181 // Test program
182 int
183 main(int argc, char** argv)
184 {
185         if (argc != 2) {
186                 fprintf(stderr, "Usage: %s FILENAME\n", argv[0]);
187                 return 1;
188         }
189
190         const char* const filename = argv[1];
191
192         RDFF file = rdff_open(filename, true);
193         if (!file)
194                 goto fail;
195
196         static const int N_URIS    = 16;
197         static const int N_RECORDS = 16;
198
199         char uri[64];
200         for (int i = 0; i < N_URIS; ++i) {
201                 snprintf(uri, sizeof(uri), "http://example.org/uri%02d", i + 1);
202                 rdff_write_uri(file, i + 1, uri, strlen(uri) + 1);
203         }
204
205         char val[6];
206         for (int i = 0; i < N_RECORDS; ++i) {
207                 snprintf(val, sizeof(val), "VAL%02d", i);
208                 rdff_write_value(file,
209                                       rand() % N_URIS,
210                                       val,
211                                       sizeof(val),
212                                       0);
213         }
214
215         rdff_close(file);
216
217         file = rdff_open(filename, false);
218         if (!file)
219                 goto fail;
220
221         RDFFChunk* chunk = malloc(sizeof(RDFFChunk));
222         chunk->size = 0;
223         for (int i = 0; i < N_URIS; ++i) {
224                 if (rdff_read_chunk(file, &chunk)
225                     || strncmp(chunk->type, "URID", 4)) {
226                         fprintf(stderr, "error: expected URID chunk\n");
227                         goto fail;
228                 }
229                 RDFFURIChunk* body = (RDFFURIChunk*)chunk->data;
230                 printf("URI: %s\n", body->uri);
231         }
232
233         for (int i = 0; i < N_RECORDS; ++i) {
234                 if (rdff_read_chunk(file, &chunk)
235                     || strncmp(chunk->type, "KVAL", 4)) {
236                         fprintf(stderr, "error: expected KVAL chunk\n");
237                         goto fail;
238                 }
239                 RDFFValueChunk* body = (RDFFValueChunk*)chunk->data;
240                 printf("KEY %d = %s\n", body->key, body->value);
241         }
242
243         free(chunk);
244         rdff_close(file);
245
246         return 0;
247
248 fail:
249         rdff_close(file);
250         fprintf(stderr, "Test failed\n");
251         return 1;
252 }
253 #endif // STANDALONE