1cabdff1aSopenharmony_ci/*
2cabdff1aSopenharmony_ci * IPFS and IPNS protocol support through IPFS Gateway.
3cabdff1aSopenharmony_ci * Copyright (c) 2022 Mark Gaiser
4cabdff1aSopenharmony_ci *
5cabdff1aSopenharmony_ci * This file is part of FFmpeg.
6cabdff1aSopenharmony_ci *
7cabdff1aSopenharmony_ci * FFmpeg is free software; you can redistribute it and/or
8cabdff1aSopenharmony_ci * modify it under the terms of the GNU Lesser General Public
9cabdff1aSopenharmony_ci * License as published by the Free Software Foundation; either
10cabdff1aSopenharmony_ci * version 2.1 of the License, or (at your option) any later version.
11cabdff1aSopenharmony_ci *
12cabdff1aSopenharmony_ci * FFmpeg is distributed in the hope that it will be useful,
13cabdff1aSopenharmony_ci * but WITHOUT ANY WARRANTY; without even the implied warranty of
14cabdff1aSopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15cabdff1aSopenharmony_ci * Lesser General Public License for more details.
16cabdff1aSopenharmony_ci *
17cabdff1aSopenharmony_ci * You should have received a copy of the GNU Lesser General Public
18cabdff1aSopenharmony_ci * License along with FFmpeg; if not, write to the Free Software
19cabdff1aSopenharmony_ci * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20cabdff1aSopenharmony_ci */
21cabdff1aSopenharmony_ci
22cabdff1aSopenharmony_ci#include "libavutil/avstring.h"
23cabdff1aSopenharmony_ci#include "libavutil/getenv_utf8.h"
24cabdff1aSopenharmony_ci#include "libavutil/opt.h"
25cabdff1aSopenharmony_ci#include <sys/stat.h>
26cabdff1aSopenharmony_ci#include "os_support.h"
27cabdff1aSopenharmony_ci#include "url.h"
28cabdff1aSopenharmony_ci
29cabdff1aSopenharmony_ci// Define the posix PATH_MAX if not there already.
30cabdff1aSopenharmony_ci// This fixes a compile issue for MSVC.
31cabdff1aSopenharmony_ci#ifndef PATH_MAX
32cabdff1aSopenharmony_ci#define PATH_MAX 4096
33cabdff1aSopenharmony_ci#endif
34cabdff1aSopenharmony_ci
35cabdff1aSopenharmony_citypedef struct IPFSGatewayContext {
36cabdff1aSopenharmony_ci    AVClass *class;
37cabdff1aSopenharmony_ci    URLContext *inner;
38cabdff1aSopenharmony_ci    // Is filled by the -gateway argument and not changed after.
39cabdff1aSopenharmony_ci    char *gateway;
40cabdff1aSopenharmony_ci    // If the above gateway is non null, it will be copied into this buffer.
41cabdff1aSopenharmony_ci    // Else this buffer will contain the auto detected gateway.
42cabdff1aSopenharmony_ci    // In either case, the gateway to use will be in this buffer.
43cabdff1aSopenharmony_ci    char gateway_buffer[PATH_MAX];
44cabdff1aSopenharmony_ci} IPFSGatewayContext;
45cabdff1aSopenharmony_ci
46cabdff1aSopenharmony_ci// A best-effort way to find the IPFS gateway.
47cabdff1aSopenharmony_ci// Only the most appropiate gateway is set. It's not actually requested
48cabdff1aSopenharmony_ci// (http call) to prevent a potential slowdown in startup. A potential timeout
49cabdff1aSopenharmony_ci// is handled by the HTTP protocol.
50cabdff1aSopenharmony_cistatic int populate_ipfs_gateway(URLContext *h)
51cabdff1aSopenharmony_ci{
52cabdff1aSopenharmony_ci    IPFSGatewayContext *c = h->priv_data;
53cabdff1aSopenharmony_ci    char ipfs_full_data_folder[PATH_MAX];
54cabdff1aSopenharmony_ci    char ipfs_gateway_file[PATH_MAX];
55cabdff1aSopenharmony_ci    struct stat st;
56cabdff1aSopenharmony_ci    int stat_ret = 0;
57cabdff1aSopenharmony_ci    int ret = AVERROR(EINVAL);
58cabdff1aSopenharmony_ci    FILE *gateway_file = NULL;
59cabdff1aSopenharmony_ci    char *env_ipfs_gateway, *env_ipfs_path;
60cabdff1aSopenharmony_ci
61cabdff1aSopenharmony_ci    // Test $IPFS_GATEWAY.
62cabdff1aSopenharmony_ci    env_ipfs_gateway = getenv_utf8("IPFS_GATEWAY");
63cabdff1aSopenharmony_ci    if (env_ipfs_gateway != NULL) {
64cabdff1aSopenharmony_ci        int printed = snprintf(c->gateway_buffer, sizeof(c->gateway_buffer),
65cabdff1aSopenharmony_ci                               "%s", env_ipfs_gateway);
66cabdff1aSopenharmony_ci        freeenv_utf8(env_ipfs_gateway);
67cabdff1aSopenharmony_ci        if (printed >= sizeof(c->gateway_buffer)) {
68cabdff1aSopenharmony_ci            av_log(h, AV_LOG_WARNING,
69cabdff1aSopenharmony_ci                   "The IPFS_GATEWAY environment variable "
70cabdff1aSopenharmony_ci                   "exceeds the maximum length. "
71cabdff1aSopenharmony_ci                   "We allow a max of %zu characters\n",
72cabdff1aSopenharmony_ci                   sizeof(c->gateway_buffer));
73cabdff1aSopenharmony_ci            ret = AVERROR(EINVAL);
74cabdff1aSopenharmony_ci            goto err;
75cabdff1aSopenharmony_ci        }
76cabdff1aSopenharmony_ci
77cabdff1aSopenharmony_ci        ret = 1;
78cabdff1aSopenharmony_ci        goto err;
79cabdff1aSopenharmony_ci    } else
80cabdff1aSopenharmony_ci        av_log(h, AV_LOG_DEBUG, "$IPFS_GATEWAY is empty.\n");
81cabdff1aSopenharmony_ci
82cabdff1aSopenharmony_ci    // We need to know the IPFS folder to - eventually - read the contents of
83cabdff1aSopenharmony_ci    // the "gateway" file which would tell us the gateway to use.
84cabdff1aSopenharmony_ci    env_ipfs_path = getenv_utf8("IPFS_PATH");
85cabdff1aSopenharmony_ci    if (env_ipfs_path == NULL) {
86cabdff1aSopenharmony_ci        int printed;
87cabdff1aSopenharmony_ci        char *env_home = getenv_utf8("HOME");
88cabdff1aSopenharmony_ci
89cabdff1aSopenharmony_ci        av_log(h, AV_LOG_DEBUG, "$IPFS_PATH is empty.\n");
90cabdff1aSopenharmony_ci
91cabdff1aSopenharmony_ci        // Try via the home folder.
92cabdff1aSopenharmony_ci        if (env_home == NULL) {
93cabdff1aSopenharmony_ci            av_log(h, AV_LOG_WARNING, "$HOME appears to be empty.\n");
94cabdff1aSopenharmony_ci            ret = AVERROR(EINVAL);
95cabdff1aSopenharmony_ci            goto err;
96cabdff1aSopenharmony_ci        }
97cabdff1aSopenharmony_ci
98cabdff1aSopenharmony_ci        // Verify the composed path fits.
99cabdff1aSopenharmony_ci        printed = snprintf(
100cabdff1aSopenharmony_ci            ipfs_full_data_folder, sizeof(ipfs_full_data_folder),
101cabdff1aSopenharmony_ci            "%s/.ipfs/", env_home);
102cabdff1aSopenharmony_ci        freeenv_utf8(env_home);
103cabdff1aSopenharmony_ci        if (printed >= sizeof(ipfs_full_data_folder)) {
104cabdff1aSopenharmony_ci            av_log(h, AV_LOG_WARNING,
105cabdff1aSopenharmony_ci                   "The IPFS data path exceeds the "
106cabdff1aSopenharmony_ci                   "max path length (%zu)\n",
107cabdff1aSopenharmony_ci                   sizeof(ipfs_full_data_folder));
108cabdff1aSopenharmony_ci            ret = AVERROR(EINVAL);
109cabdff1aSopenharmony_ci            goto err;
110cabdff1aSopenharmony_ci        }
111cabdff1aSopenharmony_ci
112cabdff1aSopenharmony_ci        // Stat the folder.
113cabdff1aSopenharmony_ci        // It should exist in a default IPFS setup when run as local user.
114cabdff1aSopenharmony_ci        stat_ret = stat(ipfs_full_data_folder, &st);
115cabdff1aSopenharmony_ci
116cabdff1aSopenharmony_ci        if (stat_ret < 0) {
117cabdff1aSopenharmony_ci            av_log(h, AV_LOG_INFO,
118cabdff1aSopenharmony_ci                   "Unable to find IPFS folder. We tried:\n"
119cabdff1aSopenharmony_ci                   "- $IPFS_PATH, which was empty.\n"
120cabdff1aSopenharmony_ci                   "- $HOME/.ipfs (full uri: %s) which doesn't exist.\n",
121cabdff1aSopenharmony_ci                   ipfs_full_data_folder);
122cabdff1aSopenharmony_ci            ret = AVERROR(ENOENT);
123cabdff1aSopenharmony_ci            goto err;
124cabdff1aSopenharmony_ci        }
125cabdff1aSopenharmony_ci    } else {
126cabdff1aSopenharmony_ci        int printed = snprintf(
127cabdff1aSopenharmony_ci            ipfs_full_data_folder, sizeof(ipfs_full_data_folder),
128cabdff1aSopenharmony_ci            "%s", env_ipfs_path);
129cabdff1aSopenharmony_ci        freeenv_utf8(env_ipfs_path);
130cabdff1aSopenharmony_ci        if (printed >= sizeof(ipfs_full_data_folder)) {
131cabdff1aSopenharmony_ci            av_log(h, AV_LOG_WARNING,
132cabdff1aSopenharmony_ci                   "The IPFS_PATH environment variable "
133cabdff1aSopenharmony_ci                   "exceeds the maximum length. "
134cabdff1aSopenharmony_ci                   "We allow a max of %zu characters\n",
135cabdff1aSopenharmony_ci                   sizeof(c->gateway_buffer));
136cabdff1aSopenharmony_ci            ret = AVERROR(EINVAL);
137cabdff1aSopenharmony_ci            goto err;
138cabdff1aSopenharmony_ci        }
139cabdff1aSopenharmony_ci    }
140cabdff1aSopenharmony_ci
141cabdff1aSopenharmony_ci    // Copy the fully composed gateway path into ipfs_gateway_file.
142cabdff1aSopenharmony_ci    if (snprintf(ipfs_gateway_file, sizeof(ipfs_gateway_file), "%sgateway",
143cabdff1aSopenharmony_ci                 ipfs_full_data_folder)
144cabdff1aSopenharmony_ci        >= sizeof(ipfs_gateway_file)) {
145cabdff1aSopenharmony_ci        av_log(h, AV_LOG_WARNING,
146cabdff1aSopenharmony_ci               "The IPFS gateway file path exceeds "
147cabdff1aSopenharmony_ci               "the max path length (%zu)\n",
148cabdff1aSopenharmony_ci               sizeof(ipfs_gateway_file));
149cabdff1aSopenharmony_ci        ret = AVERROR(ENOENT);
150cabdff1aSopenharmony_ci        goto err;
151cabdff1aSopenharmony_ci    }
152cabdff1aSopenharmony_ci
153cabdff1aSopenharmony_ci    // Get the contents of the gateway file.
154cabdff1aSopenharmony_ci    gateway_file = avpriv_fopen_utf8(ipfs_gateway_file, "r");
155cabdff1aSopenharmony_ci    if (!gateway_file) {
156cabdff1aSopenharmony_ci        av_log(h, AV_LOG_WARNING,
157cabdff1aSopenharmony_ci               "The IPFS gateway file (full uri: %s) doesn't exist. "
158cabdff1aSopenharmony_ci               "Is the gateway enabled?\n",
159cabdff1aSopenharmony_ci               ipfs_gateway_file);
160cabdff1aSopenharmony_ci        ret = AVERROR(ENOENT);
161cabdff1aSopenharmony_ci        goto err;
162cabdff1aSopenharmony_ci    }
163cabdff1aSopenharmony_ci
164cabdff1aSopenharmony_ci    // Read a single line (fgets stops at new line mark).
165cabdff1aSopenharmony_ci    if (!fgets(c->gateway_buffer, sizeof(c->gateway_buffer) - 1, gateway_file)) {
166cabdff1aSopenharmony_ci        av_log(h, AV_LOG_WARNING, "Unable to read from file (full uri: %s).\n",
167cabdff1aSopenharmony_ci               ipfs_gateway_file);
168cabdff1aSopenharmony_ci        ret = AVERROR(ENOENT);
169cabdff1aSopenharmony_ci        goto err;
170cabdff1aSopenharmony_ci    }
171cabdff1aSopenharmony_ci
172cabdff1aSopenharmony_ci    // Replace first occurence of end of line with \0
173cabdff1aSopenharmony_ci    c->gateway_buffer[strcspn(c->gateway_buffer, "\r\n")] = 0;
174cabdff1aSopenharmony_ci
175cabdff1aSopenharmony_ci    // If strlen finds anything longer then 0 characters then we have a
176cabdff1aSopenharmony_ci    // potential gateway url.
177cabdff1aSopenharmony_ci    if (*c->gateway_buffer == '\0') {
178cabdff1aSopenharmony_ci        av_log(h, AV_LOG_WARNING,
179cabdff1aSopenharmony_ci               "The IPFS gateway file (full uri: %s) appears to be empty. "
180cabdff1aSopenharmony_ci               "Is the gateway started?\n",
181cabdff1aSopenharmony_ci               ipfs_gateway_file);
182cabdff1aSopenharmony_ci        ret = AVERROR(EILSEQ);
183cabdff1aSopenharmony_ci        goto err;
184cabdff1aSopenharmony_ci    } else {
185cabdff1aSopenharmony_ci        // We're done, the c->gateway_buffer has something that looks valid.
186cabdff1aSopenharmony_ci        ret = 1;
187cabdff1aSopenharmony_ci        goto err;
188cabdff1aSopenharmony_ci    }
189cabdff1aSopenharmony_ci
190cabdff1aSopenharmony_cierr:
191cabdff1aSopenharmony_ci    if (gateway_file)
192cabdff1aSopenharmony_ci        fclose(gateway_file);
193cabdff1aSopenharmony_ci
194cabdff1aSopenharmony_ci    return ret;
195cabdff1aSopenharmony_ci}
196cabdff1aSopenharmony_ci
197cabdff1aSopenharmony_cistatic int translate_ipfs_to_http(URLContext *h, const char *uri, int flags, AVDictionary **options)
198cabdff1aSopenharmony_ci{
199cabdff1aSopenharmony_ci    const char *ipfs_cid;
200cabdff1aSopenharmony_ci    char *fulluri = NULL;
201cabdff1aSopenharmony_ci    int ret;
202cabdff1aSopenharmony_ci    IPFSGatewayContext *c = h->priv_data;
203cabdff1aSopenharmony_ci
204cabdff1aSopenharmony_ci    // Test for ipfs://, ipfs:, ipns:// and ipns:. This prefix is stripped from
205cabdff1aSopenharmony_ci    // the string leaving just the CID in ipfs_cid.
206cabdff1aSopenharmony_ci    int is_ipfs = av_stristart(uri, "ipfs://", &ipfs_cid);
207cabdff1aSopenharmony_ci    int is_ipns = av_stristart(uri, "ipns://", &ipfs_cid);
208cabdff1aSopenharmony_ci
209cabdff1aSopenharmony_ci    // We must have either ipns or ipfs.
210cabdff1aSopenharmony_ci    if (!is_ipfs && !is_ipns) {
211cabdff1aSopenharmony_ci        ret = AVERROR(EINVAL);
212cabdff1aSopenharmony_ci        av_log(h, AV_LOG_WARNING, "Unsupported url %s\n", uri);
213cabdff1aSopenharmony_ci        goto err;
214cabdff1aSopenharmony_ci    }
215cabdff1aSopenharmony_ci
216cabdff1aSopenharmony_ci    // If the CID has a length greater then 0 then we assume we have a proper working one.
217cabdff1aSopenharmony_ci    // It could still be wrong but in that case the gateway should save us and
218cabdff1aSopenharmony_ci    // ruturn a 403 error. The http protocol handles this.
219cabdff1aSopenharmony_ci    if (strlen(ipfs_cid) < 1) {
220cabdff1aSopenharmony_ci        av_log(h, AV_LOG_WARNING, "A CID must be provided.\n");
221cabdff1aSopenharmony_ci        ret = AVERROR(EILSEQ);
222cabdff1aSopenharmony_ci        goto err;
223cabdff1aSopenharmony_ci    }
224cabdff1aSopenharmony_ci
225cabdff1aSopenharmony_ci    // Populate c->gateway_buffer with whatever is in c->gateway
226cabdff1aSopenharmony_ci    if (c->gateway != NULL) {
227cabdff1aSopenharmony_ci        if (snprintf(c->gateway_buffer, sizeof(c->gateway_buffer), "%s",
228cabdff1aSopenharmony_ci                     c->gateway)
229cabdff1aSopenharmony_ci            >= sizeof(c->gateway_buffer)) {
230cabdff1aSopenharmony_ci            av_log(h, AV_LOG_WARNING,
231cabdff1aSopenharmony_ci                   "The -gateway parameter is too long. "
232cabdff1aSopenharmony_ci                   "We allow a max of %zu characters\n",
233cabdff1aSopenharmony_ci                   sizeof(c->gateway_buffer));
234cabdff1aSopenharmony_ci            ret = AVERROR(EINVAL);
235cabdff1aSopenharmony_ci            goto err;
236cabdff1aSopenharmony_ci        }
237cabdff1aSopenharmony_ci    } else {
238cabdff1aSopenharmony_ci        // Populate the IPFS gateway if we have any.
239cabdff1aSopenharmony_ci        // If not, inform the user how to properly set one.
240cabdff1aSopenharmony_ci        ret = populate_ipfs_gateway(h);
241cabdff1aSopenharmony_ci
242cabdff1aSopenharmony_ci        if (ret < 1) {
243cabdff1aSopenharmony_ci            av_log(h, AV_LOG_ERROR,
244cabdff1aSopenharmony_ci                   "IPFS does not appear to be running.\n\n"
245cabdff1aSopenharmony_ci                   "Installing IPFS locally is recommended to "
246cabdff1aSopenharmony_ci                   "improve performance and reliability, "
247cabdff1aSopenharmony_ci                   "and not share all your activity with a single IPFS gateway.\n"
248cabdff1aSopenharmony_ci                   "There are multiple options to define this gateway.\n"
249cabdff1aSopenharmony_ci                   "1. Call ffmpeg with a gateway param, "
250cabdff1aSopenharmony_ci                   "without a trailing slash: -gateway <url>.\n"
251cabdff1aSopenharmony_ci                   "2. Define an $IPFS_GATEWAY environment variable with the "
252cabdff1aSopenharmony_ci                   "full HTTP URL to the gateway "
253cabdff1aSopenharmony_ci                   "without trailing forward slash.\n"
254cabdff1aSopenharmony_ci                   "3. Define an $IPFS_PATH environment variable "
255cabdff1aSopenharmony_ci                   "and point it to the IPFS data path "
256cabdff1aSopenharmony_ci                   "- this is typically ~/.ipfs\n");
257cabdff1aSopenharmony_ci            ret = AVERROR(EINVAL);
258cabdff1aSopenharmony_ci            goto err;
259cabdff1aSopenharmony_ci        }
260cabdff1aSopenharmony_ci    }
261cabdff1aSopenharmony_ci
262cabdff1aSopenharmony_ci    // Test if the gateway starts with either http:// or https://
263cabdff1aSopenharmony_ci    if (av_stristart(c->gateway_buffer, "http://", NULL) == 0
264cabdff1aSopenharmony_ci        && av_stristart(c->gateway_buffer, "https://", NULL) == 0) {
265cabdff1aSopenharmony_ci        av_log(h, AV_LOG_WARNING,
266cabdff1aSopenharmony_ci               "The gateway URL didn't start with http:// or "
267cabdff1aSopenharmony_ci               "https:// and is therefore invalid.\n");
268cabdff1aSopenharmony_ci        ret = AVERROR(EILSEQ);
269cabdff1aSopenharmony_ci        goto err;
270cabdff1aSopenharmony_ci    }
271cabdff1aSopenharmony_ci
272cabdff1aSopenharmony_ci    // Concatenate the url.
273cabdff1aSopenharmony_ci    // This ends up with something like: http://localhost:8080/ipfs/Qm.....
274cabdff1aSopenharmony_ci    // The format of "%s%s%s%s" is the following:
275cabdff1aSopenharmony_ci    // 1st %s = The gateway.
276cabdff1aSopenharmony_ci    // 2nd %s = If the gateway didn't end in a slash, add a "/". Otherwise it's an empty string
277cabdff1aSopenharmony_ci    // 3rd %s = Either ipns/ or ipfs/.
278cabdff1aSopenharmony_ci    // 4th %s = The IPFS CID (Qm..., bafy..., ...).
279cabdff1aSopenharmony_ci    fulluri = av_asprintf("%s%s%s%s",
280cabdff1aSopenharmony_ci                          c->gateway_buffer,
281cabdff1aSopenharmony_ci                          (c->gateway_buffer[strlen(c->gateway_buffer) - 1] == '/') ? "" : "/",
282cabdff1aSopenharmony_ci                          (is_ipns) ? "ipns/" : "ipfs/",
283cabdff1aSopenharmony_ci                          ipfs_cid);
284cabdff1aSopenharmony_ci
285cabdff1aSopenharmony_ci    if (!fulluri) {
286cabdff1aSopenharmony_ci        av_log(h, AV_LOG_ERROR, "Failed to compose the URL\n");
287cabdff1aSopenharmony_ci        ret = AVERROR(ENOMEM);
288cabdff1aSopenharmony_ci        goto err;
289cabdff1aSopenharmony_ci    }
290cabdff1aSopenharmony_ci
291cabdff1aSopenharmony_ci    // Pass the URL back to FFMpeg's protocol handler.
292cabdff1aSopenharmony_ci    ret = ffurl_open_whitelist(&c->inner, fulluri, flags,
293cabdff1aSopenharmony_ci                               &h->interrupt_callback, options,
294cabdff1aSopenharmony_ci                               h->protocol_whitelist,
295cabdff1aSopenharmony_ci                               h->protocol_blacklist, h);
296cabdff1aSopenharmony_ci    if (ret < 0) {
297cabdff1aSopenharmony_ci        av_log(h, AV_LOG_WARNING, "Unable to open resource: %s\n", fulluri);
298cabdff1aSopenharmony_ci        goto err;
299cabdff1aSopenharmony_ci    }
300cabdff1aSopenharmony_ci
301cabdff1aSopenharmony_cierr:
302cabdff1aSopenharmony_ci    av_free(fulluri);
303cabdff1aSopenharmony_ci    return ret;
304cabdff1aSopenharmony_ci}
305cabdff1aSopenharmony_ci
306cabdff1aSopenharmony_cistatic int ipfs_read(URLContext *h, unsigned char *buf, int size)
307cabdff1aSopenharmony_ci{
308cabdff1aSopenharmony_ci    IPFSGatewayContext *c = h->priv_data;
309cabdff1aSopenharmony_ci    return ffurl_read(c->inner, buf, size);
310cabdff1aSopenharmony_ci}
311cabdff1aSopenharmony_ci
312cabdff1aSopenharmony_cistatic int64_t ipfs_seek(URLContext *h, int64_t pos, int whence)
313cabdff1aSopenharmony_ci{
314cabdff1aSopenharmony_ci    IPFSGatewayContext *c = h->priv_data;
315cabdff1aSopenharmony_ci    return ffurl_seek(c->inner, pos, whence);
316cabdff1aSopenharmony_ci}
317cabdff1aSopenharmony_ci
318cabdff1aSopenharmony_cistatic int ipfs_close(URLContext *h)
319cabdff1aSopenharmony_ci{
320cabdff1aSopenharmony_ci    IPFSGatewayContext *c = h->priv_data;
321cabdff1aSopenharmony_ci    return ffurl_closep(&c->inner);
322cabdff1aSopenharmony_ci}
323cabdff1aSopenharmony_ci
324cabdff1aSopenharmony_ci#define OFFSET(x) offsetof(IPFSGatewayContext, x)
325cabdff1aSopenharmony_ci
326cabdff1aSopenharmony_cistatic const AVOption options[] = {
327cabdff1aSopenharmony_ci    {"gateway", "The gateway to ask for IPFS data.", OFFSET(gateway), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, AV_OPT_FLAG_DECODING_PARAM},
328cabdff1aSopenharmony_ci    {NULL},
329cabdff1aSopenharmony_ci};
330cabdff1aSopenharmony_ci
331cabdff1aSopenharmony_cistatic const AVClass ipfs_context_class = {
332cabdff1aSopenharmony_ci    .class_name     = "IPFS",
333cabdff1aSopenharmony_ci    .item_name      = av_default_item_name,
334cabdff1aSopenharmony_ci    .option         = options,
335cabdff1aSopenharmony_ci    .version        = LIBAVUTIL_VERSION_INT,
336cabdff1aSopenharmony_ci};
337cabdff1aSopenharmony_ci
338cabdff1aSopenharmony_ciconst URLProtocol ff_ipfs_protocol = {
339cabdff1aSopenharmony_ci    .name               = "ipfs",
340cabdff1aSopenharmony_ci    .url_open2          = translate_ipfs_to_http,
341cabdff1aSopenharmony_ci    .url_read           = ipfs_read,
342cabdff1aSopenharmony_ci    .url_seek           = ipfs_seek,
343cabdff1aSopenharmony_ci    .url_close          = ipfs_close,
344cabdff1aSopenharmony_ci    .priv_data_size     = sizeof(IPFSGatewayContext),
345cabdff1aSopenharmony_ci    .priv_data_class    = &ipfs_context_class,
346cabdff1aSopenharmony_ci};
347cabdff1aSopenharmony_ci
348cabdff1aSopenharmony_ciconst URLProtocol ff_ipns_protocol = {
349cabdff1aSopenharmony_ci    .name               = "ipns",
350cabdff1aSopenharmony_ci    .url_open2          = translate_ipfs_to_http,
351cabdff1aSopenharmony_ci    .url_read           = ipfs_read,
352cabdff1aSopenharmony_ci    .url_seek           = ipfs_seek,
353cabdff1aSopenharmony_ci    .url_close          = ipfs_close,
354cabdff1aSopenharmony_ci    .priv_data_size     = sizeof(IPFSGatewayContext),
355cabdff1aSopenharmony_ci    .priv_data_class    = &ipfs_context_class,
356cabdff1aSopenharmony_ci};
357