Update opusfile from 0.8 to 0.9

This commit is contained in:
Zack Middleton 2018-03-16 13:16:41 -05:00
parent 8611eb421d
commit 58a315fe3f
7 changed files with 538 additions and 369 deletions

View file

@ -239,7 +239,8 @@ struct OpusHead{
-32768...32767. -32768...32767.
The <tt>libopusfile</tt> API will automatically apply this gain to the The <tt>libopusfile</tt> API will automatically apply this gain to the
decoded output before returning it, scaling it by decoded output before returning it, scaling it by
<code>pow(10,output_gain/(20.0*256))</code>.*/ <code>pow(10,output_gain/(20.0*256))</code>.
You can adjust this behavior with op_set_gain_offset().*/
int output_gain; int output_gain;
/**The channel mapping family, in the range 0...255. /**The channel mapping family, in the range 0...255.
Channel mapping family 0 covers mono or stereo in a single stream. Channel mapping family 0 covers mono or stereo in a single stream.
@ -1154,16 +1155,18 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_open_url(const char *_url,
int *_error,...) OP_ARG_NONNULL(1); int *_error,...) OP_ARG_NONNULL(1);
/**Open a stream using the given set of callbacks to access it. /**Open a stream using the given set of callbacks to access it.
\param _source The stream to read from (e.g., a <code>FILE *</code>). \param _stream The stream to read from (e.g., a <code>FILE *</code>).
This value will be passed verbatim as the first
argument to all of the callbacks.
\param _cb The callbacks with which to access the stream. \param _cb The callbacks with which to access the stream.
<code><a href="#op_read_func">read()</a></code> must <code><a href="#op_read_func">read()</a></code> must
be implemented. be implemented.
<code><a href="#op_seek_func">seek()</a></code> and <code><a href="#op_seek_func">seek()</a></code> and
<code><a href="#op_tell_func">tell()</a></code> may <code><a href="#op_tell_func">tell()</a></code> may
be <code>NULL</code>, or may always return -1 to be <code>NULL</code>, or may always return -1 to
indicate a source is unseekable, but if indicate a stream is unseekable, but if
<code><a href="#op_seek_func">seek()</a></code> is <code><a href="#op_seek_func">seek()</a></code> is
implemented and succeeds on a particular source, then implemented and succeeds on a particular stream, then
<code><a href="#op_tell_func">tell()</a></code> must <code><a href="#op_tell_func">tell()</a></code> must
also. also.
<code><a href="#op_close_func">close()</a></code> may <code><a href="#op_close_func">close()</a></code> may
@ -1226,11 +1229,11 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_open_url(const char *_url,
basic validity checks.</dd> basic validity checks.</dd>
</dl> </dl>
\return A freshly opened \c OggOpusFile, or <code>NULL</code> on error. \return A freshly opened \c OggOpusFile, or <code>NULL</code> on error.
<tt>libopusfile</tt> does <em>not</em> take ownership of the source <tt>libopusfile</tt> does <em>not</em> take ownership of the stream
if the call fails. if the call fails.
The calling application is responsible for closing the source if The calling application is responsible for closing the stream if
this call returns an error.*/ this call returns an error.*/
OP_WARN_UNUSED_RESULT OggOpusFile *op_open_callbacks(void *_source, OP_WARN_UNUSED_RESULT OggOpusFile *op_open_callbacks(void *_stream,
const OpusFileCallbacks *_cb,const unsigned char *_initial_data, const OpusFileCallbacks *_cb,const unsigned char *_initial_data,
size_t _initial_bytes,int *_error) OP_ARG_NONNULL(2); size_t _initial_bytes,int *_error) OP_ARG_NONNULL(2);
@ -1332,18 +1335,20 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_test_url(const char *_url,
For new code, you are likely better off using op_test() instead, which For new code, you are likely better off using op_test() instead, which
is less resource-intensive, requires less data to succeed, and imposes a is less resource-intensive, requires less data to succeed, and imposes a
hard limit on the amount of data it examines (important for unseekable hard limit on the amount of data it examines (important for unseekable
sources, where all such data must be buffered until you are sure of the streams, where all such data must be buffered until you are sure of the
stream type). stream type).
\param _source The stream to read from (e.g., a <code>FILE *</code>). \param _stream The stream to read from (e.g., a <code>FILE *</code>).
This value will be passed verbatim as the first
argument to all of the callbacks.
\param _cb The callbacks with which to access the stream. \param _cb The callbacks with which to access the stream.
<code><a href="#op_read_func">read()</a></code> must <code><a href="#op_read_func">read()</a></code> must
be implemented. be implemented.
<code><a href="#op_seek_func">seek()</a></code> and <code><a href="#op_seek_func">seek()</a></code> and
<code><a href="#op_tell_func">tell()</a></code> may <code><a href="#op_tell_func">tell()</a></code> may
be <code>NULL</code>, or may always return -1 to be <code>NULL</code>, or may always return -1 to
indicate a source is unseekable, but if indicate a stream is unseekable, but if
<code><a href="#op_seek_func">seek()</a></code> is <code><a href="#op_seek_func">seek()</a></code> is
implemented and succeeds on a particular source, then implemented and succeeds on a particular stream, then
<code><a href="#op_tell_func">tell()</a></code> must <code><a href="#op_tell_func">tell()</a></code> must
also. also.
<code><a href="#op_close_func">close()</a></code> may <code><a href="#op_close_func">close()</a></code> may
@ -1373,11 +1378,11 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_test_url(const char *_url,
See op_open_callbacks() for a full list of failure See op_open_callbacks() for a full list of failure
codes. codes.
\return A partially opened \c OggOpusFile, or <code>NULL</code> on error. \return A partially opened \c OggOpusFile, or <code>NULL</code> on error.
<tt>libopusfile</tt> does <em>not</em> take ownership of the source <tt>libopusfile</tt> does <em>not</em> take ownership of the stream
if the call fails. if the call fails.
The calling application is responsible for closing the source if The calling application is responsible for closing the stream if
this call returns an error.*/ this call returns an error.*/
OP_WARN_UNUSED_RESULT OggOpusFile *op_test_callbacks(void *_source, OP_WARN_UNUSED_RESULT OggOpusFile *op_test_callbacks(void *_stream,
const OpusFileCallbacks *_cb,const unsigned char *_initial_data, const OpusFileCallbacks *_cb,const unsigned char *_initial_data,
size_t _initial_bytes,int *_error) OP_ARG_NONNULL(2); size_t _initial_bytes,int *_error) OP_ARG_NONNULL(2);
@ -1434,7 +1439,7 @@ void op_free(OggOpusFile *_of);
Their documention will indicate so explicitly.*/ Their documention will indicate so explicitly.*/
/*@{*/ /*@{*/
/**Returns whether or not the data source being read is seekable. /**Returns whether or not the stream being read is seekable.
This is true if This is true if
<ol> <ol>
<li>The <code><a href="#op_seek_func">seek()</a></code> and <li>The <code><a href="#op_seek_func">seek()</a></code> and
@ -1455,9 +1460,9 @@ int op_seekable(const OggOpusFile *_of) OP_ARG_NONNULL(1);
return 1. return 1.
The actual number of links is not known until the stream is fully opened. The actual number of links is not known until the stream is fully opened.
\param _of The \c OggOpusFile from which to retrieve the link count. \param _of The \c OggOpusFile from which to retrieve the link count.
\return For fully-open seekable sources, this returns the total number of \return For fully-open seekable streams, this returns the total number of
links in the whole stream, which will be at least 1. links in the whole stream, which will be at least 1.
For partially-open or unseekable sources, this always returns 1.*/ For partially-open or unseekable streams, this always returns 1.*/
int op_link_count(const OggOpusFile *_of) OP_ARG_NONNULL(1); int op_link_count(const OggOpusFile *_of) OP_ARG_NONNULL(1);
/**Get the serial number of the given link in a (possibly-chained) Ogg Opus /**Get the serial number of the given link in a (possibly-chained) Ogg Opus
@ -1471,7 +1476,7 @@ int op_link_count(const OggOpusFile *_of) OP_ARG_NONNULL(1);
\return The serial number of the given link. \return The serial number of the given link.
If \a _li is greater than the total number of links, this returns If \a _li is greater than the total number of links, this returns
the serial number of the last link. the serial number of the last link.
If the source is not seekable, this always returns the serial number If the stream is not seekable, this always returns the serial number
of the current link.*/ of the current link.*/
opus_uint32 op_serialno(const OggOpusFile *_of,int _li) OP_ARG_NONNULL(1); opus_uint32 op_serialno(const OggOpusFile *_of,int _li) OP_ARG_NONNULL(1);
@ -1488,7 +1493,7 @@ opus_uint32 op_serialno(const OggOpusFile *_of,int _li) OP_ARG_NONNULL(1);
\return The channel count of the given link. \return The channel count of the given link.
If \a _li is greater than the total number of links, this returns If \a _li is greater than the total number of links, this returns
the channel count of the last link. the channel count of the last link.
If the source is not seekable, this always returns the channel count If the stream is not seekable, this always returns the channel count
of the current link.*/ of the current link.*/
int op_channel_count(const OggOpusFile *_of,int _li) OP_ARG_NONNULL(1); int op_channel_count(const OggOpusFile *_of,int _li) OP_ARG_NONNULL(1);
@ -1507,9 +1512,9 @@ int op_channel_count(const OggOpusFile *_of,int _li) OP_ARG_NONNULL(1);
compressed size of link \a _li if it is non-negative, or a negative compressed size of link \a _li if it is non-negative, or a negative
value on error. value on error.
The compressed size of the entire stream may be smaller than that The compressed size of the entire stream may be smaller than that
of the underlying source if trailing garbage was detected in the of the underlying stream if trailing garbage was detected in the
file. file.
\retval #OP_EINVAL The source is not seekable (so we can't know the length), \retval #OP_EINVAL The stream is not seekable (so we can't know the length),
\a _li wasn't less than the total number of links in \a _li wasn't less than the total number of links in
the stream, or the stream was only partially open.*/ the stream, or the stream was only partially open.*/
opus_int64 op_raw_total(const OggOpusFile *_of,int _li) OP_ARG_NONNULL(1); opus_int64 op_raw_total(const OggOpusFile *_of,int _li) OP_ARG_NONNULL(1);
@ -1527,7 +1532,7 @@ opus_int64 op_raw_total(const OggOpusFile *_of,int _li) OP_ARG_NONNULL(1);
\return The PCM length of the entire stream if \a _li is negative, the PCM \return The PCM length of the entire stream if \a _li is negative, the PCM
length of link \a _li if it is non-negative, or a negative value on length of link \a _li if it is non-negative, or a negative value on
error. error.
\retval #OP_EINVAL The source is not seekable (so we can't know the length), \retval #OP_EINVAL The stream is not seekable (so we can't know the length),
\a _li wasn't less than the total number of links in \a _li wasn't less than the total number of links in
the stream, or the stream was only partially open.*/ the stream, or the stream was only partially open.*/
ogg_int64_t op_pcm_total(const OggOpusFile *_of,int _li) OP_ARG_NONNULL(1); ogg_int64_t op_pcm_total(const OggOpusFile *_of,int _li) OP_ARG_NONNULL(1);
@ -1575,8 +1580,8 @@ const OpusTags *op_tags(const OggOpusFile *_of,int _li) OP_ARG_NONNULL(1);
\param _of The \c OggOpusFile from which to retrieve the current link index. \param _of The \c OggOpusFile from which to retrieve the current link index.
\return The index of the current link on success, or a negative value on \return The index of the current link on success, or a negative value on
failure. failure.
For seekable streams, this is a number between 0 and the value For seekable streams, this is a number between 0 (inclusive) and the
returned by op_link_count(). value returned by op_link_count() (exclusive).
For unseekable streams, this value starts at 0 and increments by one For unseekable streams, this value starts at 0 and increments by one
each time a new link is encountered (even though op_link_count() each time a new link is encountered (even though op_link_count()
always returns 1). always returns 1).
@ -1640,10 +1645,10 @@ ogg_int64_t op_pcm_tell(const OggOpusFile *_of) OP_ARG_NONNULL(1);
/*@{*/ /*@{*/
/**\name Functions for seeking in Opus streams /**\name Functions for seeking in Opus streams
These functions let you seek in Opus streams, if the underlying source These functions let you seek in Opus streams, if the underlying stream
support it. support it.
Seeking is implemented for all built-in stream I/O routines, though some Seeking is implemented for all built-in stream I/O routines, though some
individual sources may not be seekable (pipes, live HTTP streams, or HTTP individual streams may not be seekable (pipes, live HTTP streams, or HTTP
streams from a server that does not support <code>Range</code> requests). streams from a server that does not support <code>Range</code> requests).
op_raw_seek() is the fastest: it is guaranteed to perform at most one op_raw_seek() is the fastest: it is guaranteed to perform at most one
@ -1670,6 +1675,8 @@ ogg_int64_t op_pcm_tell(const OggOpusFile *_of) OP_ARG_NONNULL(1);
packets out of the tail of the link to which it seeks. packets out of the tail of the link to which it seeks.
\param _of The \c OggOpusFile in which to seek. \param _of The \c OggOpusFile in which to seek.
\param _byte_offset The byte position to seek to. \param _byte_offset The byte position to seek to.
This must be between 0 and #op_raw_total(\a _of,\c -1)
(inclusive).
\return 0 on success, or a negative error code on failure. \return 0 on success, or a negative error code on failure.
\retval #OP_EREAD The underlying seek operation failed. \retval #OP_EREAD The underlying seek operation failed.
\retval #OP_EINVAL The stream was only partially open, or the target was \retval #OP_EINVAL The stream was only partially open, or the target was

View file

@ -214,6 +214,7 @@ static const char *op_parse_file_url(const char *_src){
# include <winsock2.h> # include <winsock2.h>
# include <ws2tcpip.h> # include <ws2tcpip.h>
# include <openssl/ssl.h> # include <openssl/ssl.h>
# include <openssl/asn1.h>
# include "winerrno.h" # include "winerrno.h"
typedef SOCKET op_sock; typedef SOCKET op_sock;
@ -307,6 +308,12 @@ static int op_poll_win32(struct pollfd *_fds,nfds_t _nfds,int _timeout){
operate on sockets, because we don't use non-socket I/O here, and this operate on sockets, because we don't use non-socket I/O here, and this
minimizes the changes needed to deal with Winsock.*/ minimizes the changes needed to deal with Winsock.*/
# define close(_fd) closesocket(_fd) # define close(_fd) closesocket(_fd)
/*This takes an int for the address length, even though the value is of type
socklen_t (defined as an unsigned integer type with at least 32 bits).*/
# define connect(_fd,_addr,_addrlen) \
(OP_UNLIKELY((_addrlen)>(socklen_t)INT_MAX)? \
WSASetLastError(WSA_NOT_ENOUGH_MEMORY),-1: \
connect(_fd,_addr,(int)(_addrlen)))
/*This relies on sizeof(u_long)==sizeof(int), which is always true on both /*This relies on sizeof(u_long)==sizeof(int), which is always true on both
Win32 and Win64.*/ Win32 and Win64.*/
# define ioctl(_fd,_req,_arg) ioctlsocket(_fd,_req,(u_long *)(_arg)) # define ioctl(_fd,_req,_arg) ioctlsocket(_fd,_req,(u_long *)(_arg))
@ -338,6 +345,7 @@ int SSL_CTX_set_default_verify_paths_win32(SSL_CTX *_ssl_ctx);
# include <poll.h> # include <poll.h>
# include <unistd.h> # include <unistd.h>
# include <openssl/ssl.h> # include <openssl/ssl.h>
# include <openssl/asn1.h>
typedef int op_sock; typedef int op_sock;
@ -471,7 +479,7 @@ static int op_parse_url_impl(OpusParsedURL *_dst,const char *_src){
scheme_end=_src+strspn(_src,OP_URL_SCHEME); scheme_end=_src+strspn(_src,OP_URL_SCHEME);
if(OP_UNLIKELY(*scheme_end!=':') if(OP_UNLIKELY(*scheme_end!=':')
||OP_UNLIKELY(scheme_end-_src<4)||OP_UNLIKELY(scheme_end-_src>5) ||OP_UNLIKELY(scheme_end-_src<4)||OP_UNLIKELY(scheme_end-_src>5)
||OP_UNLIKELY(op_strncasecmp(_src,"https",scheme_end-_src)!=0)){ ||OP_UNLIKELY(op_strncasecmp(_src,"https",(int)(scheme_end-_src))!=0)){
/*Unsupported protocol.*/ /*Unsupported protocol.*/
return OP_EIMPL; return OP_EIMPL;
} }
@ -674,7 +682,10 @@ static int op_sb_append(OpusStringBuf *_sb,const char *_s,int _len){
} }
static int op_sb_append_string(OpusStringBuf *_sb,const char *_s){ static int op_sb_append_string(OpusStringBuf *_sb,const char *_s){
return op_sb_append(_sb,_s,strlen(_s)); size_t len;
len=strlen(_s);
if(OP_UNLIKELY(len>(size_t)INT_MAX))return OP_EFAULT;
return op_sb_append(_sb,_s,(int)len);
} }
static int op_sb_append_port(OpusStringBuf *_sb,unsigned _port){ static int op_sb_append_port(OpusStringBuf *_sb,unsigned _port){
@ -962,7 +973,8 @@ static int op_http_conn_write_fully(OpusHTTPConn *_conn,
ret=send(fd.fd,_buf,_buf_size,0); ret=send(fd.fd,_buf,_buf_size,0);
if(ret>0){ if(ret>0){
_buf+=ret; _buf+=ret;
_buf_size-=ret; OP_ASSERT(ret<=_buf_size);
_buf_size-=(int)ret;
continue; continue;
} }
err=op_errno(); err=op_errno();
@ -1077,8 +1089,9 @@ static int op_http_conn_read(OpusHTTPConn *_conn,
if(ret>0){ if(ret>0){
/*Read some data. /*Read some data.
Keep going to see if there's more.*/ Keep going to see if there's more.*/
nread+=ret; OP_ASSERT(ret<=_buf_size-nread);
nread_unblocked+=ret; nread+=(int)ret;
nread_unblocked+=(int)ret;
continue; continue;
} }
/*If we already read some data or the connection was closed, return /*If we already read some data or the connection was closed, return
@ -1171,8 +1184,8 @@ static int op_http_conn_read_response(OpusHTTPConn *_conn,
if(OP_UNLIKELY(ret<=0))return size<=0?OP_EREAD:OP_FALSE; if(OP_UNLIKELY(ret<=0))return size<=0?OP_EREAD:OP_FALSE;
/*We read some data.*/ /*We read some data.*/
/*Make sure the starting characters are "HTTP". /*Make sure the starting characters are "HTTP".
Otherwise we could wind up waiting forever for a response from Otherwise we could wind up waiting for a response from something that is
something that is not an HTTP server.*/ not an HTTP server until we time out.*/
if(size<4&&op_strncasecmp(buf,"HTTP",OP_MIN(size+ret,4))!=0){ if(size<4&&op_strncasecmp(buf,"HTTP",OP_MIN(size+ret,4))!=0){
return OP_FALSE; return OP_FALSE;
} }
@ -1245,10 +1258,10 @@ static char *op_http_parse_status_line(int *_v1_1_compat,
char *status_code; char *status_code;
int v1_1_compat; int v1_1_compat;
size_t d; size_t d;
/*RFC 2616 Section 6.1 does not say that the tokens in the Status-Line cannot /*RFC 2616 Section 6.1 does not say if the tokens in the Status-Line can be
be separated by optional LWS, but since it specifically calls out where separated by optional LWS, but since it specifically calls out where
spaces are to be placed and that CR and LF are not allowed except at the spaces are to be placed and that CR and LF are not allowed except at the
end, I am assuming this to be true.*/ end, we are assuming extra LWS is not allowed.*/
/*We already validated that this starts with "HTTP"*/ /*We already validated that this starts with "HTTP"*/
OP_ASSERT(op_strncasecmp(_response,"HTTP",4)==0); OP_ASSERT(op_strncasecmp(_response,"HTTP",4)==0);
next=_response+4; next=_response+4;
@ -1272,7 +1285,7 @@ static char *op_http_parse_status_line(int *_v1_1_compat,
d--; d--;
} }
/*We don't need to parse the version number. /*We don't need to parse the version number.
Any non-zero digit means it's greater than 1.*/ Any non-zero digit means it's at least 1.*/
v1_1_compat=d>0; v1_1_compat=d>0;
next+=d; next+=d;
if(OP_UNLIKELY(*next++!=' '))return NULL; if(OP_UNLIKELY(*next++!=' '))return NULL;
@ -1520,6 +1533,7 @@ static long op_bio_retry_ctrl(BIO *_b,int _cmd,long _num,void *_ptr){
# if OPENSSL_VERSION_NUMBER<0x10100000L # if OPENSSL_VERSION_NUMBER<0x10100000L
# define BIO_set_data(_b,_ptr) ((_b)->ptr=(_ptr)) # define BIO_set_data(_b,_ptr) ((_b)->ptr=(_ptr))
# define BIO_set_init(_b,_init) ((_b)->init=(_init)) # define BIO_set_init(_b,_init) ((_b)->init=(_init))
# define ASN1_STRING_get0_data ASN1_STRING_data
# endif # endif
static int op_bio_retry_new(BIO *_b){ static int op_bio_retry_new(BIO *_b){
@ -1603,11 +1617,25 @@ static int op_http_conn_establish_tunnel(OpusHTTPStream *_stream,
next=op_http_parse_status_line(NULL,&status_code,_stream->response.buf); next=op_http_parse_status_line(NULL,&status_code,_stream->response.buf);
/*According to RFC 2817, "Any successful (2xx) response to a /*According to RFC 2817, "Any successful (2xx) response to a
CONNECT request indicates that the proxy has established a CONNECT request indicates that the proxy has established a
connection to the requested host and port.*/ connection to the requested host and port."*/
if(OP_UNLIKELY(next==NULL)||OP_UNLIKELY(status_code[0]!='2'))return OP_FALSE; if(OP_UNLIKELY(next==NULL)||OP_UNLIKELY(status_code[0]!='2'))return OP_FALSE;
return 0; return 0;
} }
/*Convert a host to a numeric address, if possible.
Return: A struct addrinfo containing the address, if it was numeric, and NULL
otherwise.*/
static struct addrinfo *op_inet_pton(const char *_host){
struct addrinfo *addrs;
struct addrinfo hints;
memset(&hints,0,sizeof(hints));
hints.ai_socktype=SOCK_STREAM;
hints.ai_flags=AI_NUMERICHOST;
if(!getaddrinfo(_host,NULL,&hints,&addrs))return addrs;
return NULL;
}
# if OPENSSL_VERSION_NUMBER<0x10002000L
/*Match a host name against a host with a possible wildcard pattern according /*Match a host name against a host with a possible wildcard pattern according
to the rules of RFC 6125 Section 6.4.3. to the rules of RFC 6125 Section 6.4.3.
Return: 0 if the pattern doesn't match, and a non-zero value if it does.*/ Return: 0 if the pattern doesn't match, and a non-zero value if it does.*/
@ -1620,13 +1648,15 @@ static int op_http_hostname_match(const char *_host,size_t _host_len,
size_t pattern_label_len; size_t pattern_label_len;
size_t pattern_prefix_len; size_t pattern_prefix_len;
size_t pattern_suffix_len; size_t pattern_suffix_len;
pattern=(const char *)ASN1_STRING_data(_pattern); if(OP_UNLIKELY(_host_len>(size_t)INT_MAX))return 0;
pattern=(const char *)ASN1_STRING_get0_data(_pattern);
pattern_len=strlen(pattern); pattern_len=strlen(pattern);
/*Check the pattern for embedded NULs.*/ /*Check the pattern for embedded NULs.*/
if(OP_UNLIKELY(pattern_len!=(size_t)ASN1_STRING_length(_pattern)))return 0; if(OP_UNLIKELY(pattern_len!=(size_t)ASN1_STRING_length(_pattern)))return 0;
pattern_label_len=strcspn(pattern,"."); pattern_label_len=strcspn(pattern,".");
OP_ASSERT(pattern_label_len<=pattern_len); OP_ASSERT(pattern_label_len<=pattern_len);
pattern_prefix_len=strcspn(pattern,"*"); pattern_prefix_len=strcspn(pattern,"*");
if(OP_UNLIKELY(pattern_prefix_len>(size_t)INT_MAX))return 0;
if(pattern_prefix_len>=pattern_label_len){ if(pattern_prefix_len>=pattern_label_len){
/*"The client SHOULD NOT attempt to match a presented identifier in which /*"The client SHOULD NOT attempt to match a presented identifier in which
the wildcard character comprises a label other than the left-most label the wildcard character comprises a label other than the left-most label
@ -1637,7 +1667,8 @@ static int op_http_hostname_match(const char *_host,size_t _host_len,
Don't use the system strcasecmp here, as that uses the locale and Don't use the system strcasecmp here, as that uses the locale and
RFC 4343 makes clear that DNS's case-insensitivity only applies to RFC 4343 makes clear that DNS's case-insensitivity only applies to
the ASCII range.*/ the ASCII range.*/
return _host_len==pattern_len&&op_strncasecmp(_host,pattern,_host_len)==0; return _host_len==pattern_len
&&op_strncasecmp(_host,pattern,(int)_host_len)==0;
} }
/*"However, the client SHOULD NOT attempt to match a presented identifier /*"However, the client SHOULD NOT attempt to match a presented identifier
where the wildcard character is embedded within an A-label or U-label of where the wildcard character is embedded within an A-label or U-label of
@ -1672,34 +1703,25 @@ static int op_http_hostname_match(const char *_host,size_t _host_len,
pattern_suffix_len=pattern_len-pattern_prefix_len-1; pattern_suffix_len=pattern_len-pattern_prefix_len-1;
host_suffix_len=_host_len-host_label_len host_suffix_len=_host_len-host_label_len
+pattern_label_len-pattern_prefix_len-1; +pattern_label_len-pattern_prefix_len-1;
OP_ASSERT(host_suffix_len<=_host_len);
return pattern_suffix_len==host_suffix_len return pattern_suffix_len==host_suffix_len
&&op_strncasecmp(_host,pattern,pattern_prefix_len)==0 &&op_strncasecmp(_host,pattern,(int)pattern_prefix_len)==0
&&op_strncasecmp(_host+_host_len-host_suffix_len, &&op_strncasecmp(_host+_host_len-host_suffix_len,
pattern+pattern_prefix_len+1,host_suffix_len)==0; pattern+pattern_prefix_len+1,(int)host_suffix_len)==0;
}
/*Convert a host to a numeric address, if possible.
Return: A struct addrinfo containing the address, if it was numeric, and NULL
otherise.*/
static struct addrinfo *op_inet_pton(const char *_host){
struct addrinfo *addrs;
struct addrinfo hints;
memset(&hints,0,sizeof(hints));
hints.ai_socktype=SOCK_STREAM;
hints.ai_flags=AI_NUMERICHOST;
if(!getaddrinfo(_host,NULL,&hints,&addrs))return addrs;
return NULL;
} }
/*Verify the server's hostname matches the certificate they presented using /*Verify the server's hostname matches the certificate they presented using
the procedure from Section 6 of RFC 6125. the procedure from Section 6 of RFC 6125.
Return: 0 if the certificate doesn't match, and a non-zero value if it does.*/ Return: 0 if the certificate doesn't match, and a non-zero value if it does.*/
static int op_http_verify_hostname(OpusHTTPStream *_stream,SSL *_ssl_conn){ static int op_http_verify_hostname(OpusHTTPStream *_stream,SSL *_ssl_conn){
X509 *peer_cert; X509 *peer_cert;
STACK_OF(GENERAL_NAME) *san_names; struct addrinfo *addr;
char *host; char *host;
size_t host_len; size_t host_len;
int ret; unsigned char *ip;
int ip_len;
int check_cn;
int ret;
host=_stream->url.host; host=_stream->url.host;
host_len=strlen(host); host_len=strlen(host);
peer_cert=SSL_get_peer_certificate(_ssl_conn); peer_cert=SSL_get_peer_certificate(_ssl_conn);
@ -1707,142 +1729,154 @@ static int op_http_verify_hostname(OpusHTTPStream *_stream,SSL *_ssl_conn){
if(OP_UNLIKELY(peer_cert==NULL))return 0; if(OP_UNLIKELY(peer_cert==NULL))return 0;
ret=0; ret=0;
OP_ASSERT(host_len<INT_MAX); OP_ASSERT(host_len<INT_MAX);
/*RFC 2818 says (after correcting for Eratta 1077): "If a subjectAltName /*By default, fall back to checking the Common Name if we don't check any
extension of type dNSName is present, that MUST be used as the identity. subjectAltNames of type dNSName.*/
Otherwise, the (most specific) Common Name field in the Subject field of check_cn=1;
the certificate MUST be used. /*Check to see if the host was specified as a simple IP address.*/
Although the use of the Common Name is existing practice, it is deprecated addr=op_inet_pton(host);
and Certification Authorities are encouraged to use the dNSName ip=NULL;
instead." ip_len=0;
"Matching is performed using the matching rules specified by RFC 2459. if(addr!=NULL){
If more than one identity of a given type is present in the certificate switch(addr->ai_family){
(e.g., more than one dNSName name), a match in any one of the set is case AF_INET:{
considered acceptable. struct sockaddr_in *s;
Names may contain the wildcard character * which is condered to match any s=(struct sockaddr_in *)addr->ai_addr;
single domain name component or component fragment. OP_ASSERT(addr->ai_addrlen>=sizeof(*s));
E.g., *.a.com matches foo.a.com but not bar.foo.a.com. ip=(unsigned char *)&s->sin_addr;
f*.com matches foo.com but not bar.com." ip_len=sizeof(s->sin_addr);
"In some cases, the URI is specified as an IP address rather than a /*RFC 6125 says, "In this case, the iPAddress subjectAltName must [sic]
hostname. be present in the certificate and must [sic] exactly match the IP in
In this case, the iPAddress subjectAltName must be present in the the URI."
certificate and must exactly match the IP in the URI."*/ So don't allow falling back to a Common Name.*/
san_names=X509_get_ext_d2i(peer_cert,NID_subject_alt_name,NULL,NULL); check_cn=0;
if(san_names!=NULL){ }break;
struct addrinfo *addr; case AF_INET6:{
unsigned char *ip; struct sockaddr_in6 *s;
int ip_len; s=(struct sockaddr_in6 *)addr->ai_addr;
int nsan_names; OP_ASSERT(addr->ai_addrlen>=sizeof(*s));
int sni; ip=(unsigned char *)&s->sin6_addr;
/*Check to see if the host was specified as a simple IP address.*/ ip_len=sizeof(s->sin6_addr);
addr=op_inet_pton(host); check_cn=0;
ip=NULL; }break;
ip_len=0;
if(addr!=NULL){
switch(addr->ai_family){
case AF_INET:{
struct sockaddr_in *s;
s=(struct sockaddr_in *)addr->ai_addr;
OP_ASSERT(addr->ai_addrlen>=sizeof(*s));
ip=(unsigned char *)&s->sin_addr;
ip_len=sizeof(s->sin_addr);
}break;
case AF_INET6:{
struct sockaddr_in6 *s;
s=(struct sockaddr_in6 *)addr->ai_addr;
OP_ASSERT(addr->ai_addrlen>=sizeof(*s));
ip=(unsigned char *)&s->sin6_addr;
ip_len=sizeof(s->sin6_addr);
}break;
}
} }
/*We can only verify fully-qualified domain names. }
To quote RFC 6125: "The extracted data MUST include only information that /*We can only verify IP addresses and "fully-qualified" domain names.
can be securely parsed out of the inputs (e.g., parsing the fully To quote RFC 6125: "The extracted data MUST include only information that
qualified DNS domain name out of the "host" component (or its can be securely parsed out of the inputs (e.g., parsing the fully
equivalent) of a URI or deriving the application service type from the qualified DNS domain name out of the "host" component (or its
scheme of a URI) ..." equivalent) of a URI or deriving the application service type from the
We don't have a way to check (without relying on DNS records, which might scheme of a URI) ..."
be subverted) if this address is fully-qualified. We don't have a way to check (without relying on DNS records, which might
This is particularly problematic when using a CONNECT tunnel, as it is be subverted) if this address is fully-qualified.
the server that does DNS lookup, not us. This is particularly problematic when using a CONNECT tunnel, as it is
However, we are certain that if the hostname has no '.', it is definitely the server that does DNS lookup, not us.
not a fully-qualified domain name (with the exception of crazy TLDs that However, we are certain that if the hostname has no '.', it is definitely
actually resolve, like "uz", but I am willing to ignore those). not a fully-qualified domain name (with the exception of crazy TLDs that
RFC 1535 says "...in any event where a '.' exists in a specified name it actually resolve, like "uz", but I am willing to ignore those).
should be assumed to be a fully qualified domain name (FQDN) and SHOULD RFC 1535 says "...in any event where a '.' exists in a specified name it
be tried as a rooted name first." should be assumed to be a fully qualified domain name (FQDN) and SHOULD
That doesn't give us any security guarantees, of course (a subverted DNS be tried as a rooted name first."
could fail the original query and our resolver might still retry with a That doesn't give us any security guarantees, of course (a subverted DNS
local domain appended). could fail the original query and our resolver might still retry with a
If we don't have a FQDN, just set the number of names to 0, so we'll fail local domain appended).*/
and clean up any resources we allocated.*/ if(ip!=NULL||strchr(host,'.')!=NULL){
if(ip==NULL&&strchr(host,'.')==NULL)nsan_names=0; STACK_OF(GENERAL_NAME) *san_names;
/*RFC 2459 says there MUST be at least one, but we don't depend on it.*/ /*RFC 2818 says (after correcting for Eratta 1077): "If a subjectAltName
else nsan_names=sk_GENERAL_NAME_num(san_names); extension of type dNSName is present, that MUST be used as the identity.
for(sni=0;sni<nsan_names;sni++){ Otherwise, the (most specific) Common Name field in the Subject field of
const GENERAL_NAME *name; the certificate MUST be used.
name=sk_GENERAL_NAME_value(san_names,sni); Although the use of the Common Name is existing practice, it is
if(ip==NULL){ deprecated and Certification Authorities are encouraged to use the
if(name->type==GEN_DNS dNSName instead."
&&op_http_hostname_match(host,host_len,name->d.dNSName)){ "Matching is performed using the matching rules specified by RFC 2459.
ret=1; If more than one identity of a given type is present in the certificate
break; (e.g., more than one dNSName name), a match in any one of the set is
considered acceptable.
Names may contain the wildcard character * which is condered to match any
single domain name component or component fragment.
E.g., *.a.com matches foo.a.com but not bar.foo.a.com.
f*.com matches foo.com but not bar.com."
"In some cases, the URI is specified as an IP address rather than a
hostname.
In this case, the iPAddress subjectAltName must be present in the
certificate and must exactly match the IP in the URI."*/
san_names=X509_get_ext_d2i(peer_cert,NID_subject_alt_name,NULL,NULL);
if(san_names!=NULL){
int nsan_names;
int sni;
/*RFC 2459 says there MUST be at least one, but we don't depend on it.*/
nsan_names=sk_GENERAL_NAME_num(san_names);
for(sni=0;sni<nsan_names;sni++){
const GENERAL_NAME *name;
name=sk_GENERAL_NAME_value(san_names,sni);
if(ip==NULL){
if(name->type==GEN_DNS){
/*We have a subjectAltName extension of type dNSName, so don't fall
back to a Common Name.
https://marc.info/?l=openssl-dev&m=139617145216047&w=2 says that
subjectAltNames of other types do not trigger this restriction,
(e.g., if they are all IP addresses, we will still check a
non-IP hostname against a Common Name).*/
check_cn=0;
if(op_http_hostname_match(host,host_len,name->d.dNSName)){
ret=1;
break;
}
}
}
else if(name->type==GEN_IPADD){
unsigned const char *cert_ip;
/*If we do have an IP address, compare it directly.
RFC 6125: "When the reference identity is an IP address, the
identity MUST be converted to the 'network byte order' octet
string representation.
For IP Version 4, as specified in RFC 791, the octet string will
contain exactly four octets.
For IP Version 6, as specified in RFC 2460, the octet string will
contain exactly sixteen octets.
This octet string is then compared against subjectAltName values of
type iPAddress.
A match occurs if the reference identity octet string and the value
octet strings are identical."*/
cert_ip=ASN1_STRING_get0_data(name->d.iPAddress);
if(ip_len==ASN1_STRING_length(name->d.iPAddress)
&&memcmp(ip,cert_ip,ip_len)==0){
ret=1;
break;
}
} }
} }
else if(name->type==GEN_IPADD){ sk_GENERAL_NAME_pop_free(san_names,GENERAL_NAME_free);
unsigned char *cert_ip; }
/*If we do have an IP address, compare it directly. /*If we're supposed to fall back to a Common Name, match against it here.*/
RFC 6125: "When the reference identity is an IP address, the identity if(check_cn){
MUST be converted to the 'network byte order' octet string int last_cn_loc;
representation. int cn_loc;
For IP Version 4, as specified in RFC 791, the octet string will /*RFC 6125 says that at least one significant CA is known to issue certs
contain exactly four octets. with multiple CNs, although it SHOULD NOT.
For IP Version 6, as specified in RFC 2460, the octet string will It also says: "The server's identity may also be verified by comparing
contain exactly sixteen octets. the reference identity to the Common Name (CN) value in the last
This octet string is then compared against subjectAltName values of Relative Distinguished Name (RDN) of the subject field of the server's
type iPAddress. certificate (where "last" refers to the DER-encoded order...)."
A match occurs if the reference identity octet string and the value So find the last one and check it.*/
octet strings are identical."*/ cn_loc=-1;
cert_ip=ASN1_STRING_data(name->d.iPAddress); do{
if(ip_len==ASN1_STRING_length(name->d.iPAddress) last_cn_loc=cn_loc;
&&memcmp(ip,cert_ip,ip_len)==0){ cn_loc=X509_NAME_get_index_by_NID(X509_get_subject_name(peer_cert),
ret=1; NID_commonName,last_cn_loc);
break;
}
} }
while(cn_loc>=0);
ret=last_cn_loc>=0
&&op_http_hostname_match(host,host_len,
X509_NAME_ENTRY_get_data(
X509_NAME_get_entry(X509_get_subject_name(peer_cert),last_cn_loc)));
} }
sk_GENERAL_NAME_pop_free(san_names,GENERAL_NAME_free);
if(addr!=NULL)freeaddrinfo(addr);
}
/*Do the same FQDN check we did above.
We don't do this once in advance for both cases, because in the
subjectAltName case we might have an IPv6 address without a dot.*/
else if(strchr(host,'.')!=NULL){
int last_cn_loc;
int cn_loc;
/*If there is no subjectAltName, match against commonName.
RFC 6125 says that at least one significant CA is known to issue certs
with multiple CNs, although it SHOULD NOT.
It also says: "The server's identity may also be verified by comparing
the reference identity to the Common Name (CN) value in the last
Relative Distinguished Name (RDN) of the subject field of the server's
certificate (where "last" refers to the DER-encoded order...)."
So find the last one and check it.*/
cn_loc=-1;
do{
last_cn_loc=cn_loc;
cn_loc=X509_NAME_get_index_by_NID(X509_get_subject_name(peer_cert),
NID_commonName,last_cn_loc);
}
while(cn_loc>=0);
ret=last_cn_loc>=0
&&op_http_hostname_match(host,host_len,
X509_NAME_ENTRY_get_data(
X509_NAME_get_entry(X509_get_subject_name(peer_cert),last_cn_loc)));
} }
if(addr!=NULL)freeaddrinfo(addr);
X509_free(peer_cert); X509_free(peer_cert);
return ret; return ret;
} }
# endif
/*Perform the TLS handshake on a new connection.*/ /*Perform the TLS handshake on a new connection.*/
static int op_http_conn_start_tls(OpusHTTPStream *_stream,OpusHTTPConn *_conn, static int op_http_conn_start_tls(OpusHTTPStream *_stream,OpusHTTPConn *_conn,
@ -1851,11 +1885,56 @@ static int op_http_conn_start_tls(OpusHTTPStream *_stream,OpusHTTPConn *_conn,
BIO *ssl_bio; BIO *ssl_bio;
int skip_certificate_check; int skip_certificate_check;
int ret; int ret;
ssl_bio=BIO_new_socket(_fd,BIO_NOCLOSE); /*This always takes an int, even though with Winsock op_sock is a SOCKET.*/
ssl_bio=BIO_new_socket((int)_fd,BIO_NOCLOSE);
if(OP_LIKELY(ssl_bio==NULL))return OP_FALSE; if(OP_LIKELY(ssl_bio==NULL))return OP_FALSE;
# if !defined(OPENSSL_NO_TLSEXT) # if !defined(OPENSSL_NO_TLSEXT)
/*Support for RFC 6066 Server Name Indication.*/ /*Support for RFC 6066 Server Name Indication.*/
SSL_set_tlsext_host_name(_ssl_conn,_stream->url.host); SSL_set_tlsext_host_name(_ssl_conn,_stream->url.host);
# endif
skip_certificate_check=_stream->skip_certificate_check;
# if OPENSSL_VERSION_NUMBER>=0x10002000L
/*As of version 1.0.2, OpenSSL can finally do hostname checks automatically.
Of course, they make it much more complicated than it needs to be.*/
if(!skip_certificate_check){
X509_VERIFY_PARAM *param;
struct addrinfo *addr;
char *host;
unsigned char *ip;
int ip_len;
param=SSL_get0_param(_ssl_conn);
OP_ASSERT(param!=NULL);
host=_stream->url.host;
ip=NULL;
ip_len=0;
/*Check to see if the host was specified as a simple IP address.*/
addr=op_inet_pton(host);
if(addr!=NULL){
switch(addr->ai_family){
case AF_INET:{
struct sockaddr_in *s;
s=(struct sockaddr_in *)addr->ai_addr;
OP_ASSERT(addr->ai_addrlen>=sizeof(*s));
ip=(unsigned char *)&s->sin_addr;
ip_len=sizeof(s->sin_addr);
host=NULL;
}break;
case AF_INET6:{
struct sockaddr_in6 *s;
s=(struct sockaddr_in6 *)addr->ai_addr;
OP_ASSERT(addr->ai_addrlen>=sizeof(*s));
ip=(unsigned char *)&s->sin6_addr;
ip_len=sizeof(s->sin6_addr);
host=NULL;
}break;
}
}
/*Always set both host and ip to prevent matching against an old one.
One of the two will always be NULL, clearing that parameter.*/
X509_VERIFY_PARAM_set1_host(param,host,0);
X509_VERIFY_PARAM_set1_ip(param,ip,ip_len);
if(addr!=NULL)freeaddrinfo(addr);
}
# endif # endif
/*Resume a previous session if available.*/ /*Resume a previous session if available.*/
if(_stream->ssl_session!=NULL){ if(_stream->ssl_session!=NULL){
@ -1876,17 +1955,22 @@ static int op_http_conn_start_tls(OpusHTTPStream *_stream,OpusHTTPConn *_conn,
ret=op_do_ssl_step(_ssl_conn,_fd,SSL_connect); ret=op_do_ssl_step(_ssl_conn,_fd,SSL_connect);
if(OP_UNLIKELY(ret<=0))return OP_FALSE; if(OP_UNLIKELY(ret<=0))return OP_FALSE;
ssl_session=_stream->ssl_session; ssl_session=_stream->ssl_session;
skip_certificate_check=_stream->skip_certificate_check; if(ssl_session==NULL
if(ssl_session==NULL||!skip_certificate_check){ # if OPENSSL_VERSION_NUMBER<0x10002000L
||!skip_certificate_check
# endif
){
ret=op_do_ssl_step(_ssl_conn,_fd,SSL_do_handshake); ret=op_do_ssl_step(_ssl_conn,_fd,SSL_do_handshake);
if(OP_UNLIKELY(ret<=0))return OP_FALSE; if(OP_UNLIKELY(ret<=0))return OP_FALSE;
/*OpenSSL does not do hostname verification, despite the fact that we just # if OPENSSL_VERSION_NUMBER<0x10002000L
passed it the hostname above in the call to SSL_set_tlsext_host_name(), /*OpenSSL before version 1.0.2 does not do automatic hostname verification,
because they are morons. despite the fact that we just passed it the hostname above in the call
to SSL_set_tlsext_host_name().
Do it for them.*/ Do it for them.*/
if(!skip_certificate_check&&!op_http_verify_hostname(_stream,_ssl_conn)){ if(!skip_certificate_check&&!op_http_verify_hostname(_stream,_ssl_conn)){
return OP_FALSE; return OP_FALSE;
} }
# endif
if(ssl_session==NULL){ if(ssl_session==NULL){
/*Save the session for later resumption.*/ /*Save the session for later resumption.*/
_stream->ssl_session=SSL_get1_session(_ssl_conn); _stream->ssl_session=SSL_get1_session(_ssl_conn);
@ -1911,11 +1995,10 @@ static int op_http_conn_start_tls(OpusHTTPStream *_stream,OpusHTTPConn *_conn,
left to try. left to try.
*_addr will be set to NULL in this case.*/ *_addr will be set to NULL in this case.*/
static int op_sock_connect_next(op_sock _fd, static int op_sock_connect_next(op_sock _fd,
const struct addrinfo **_addr,int _ai_family){ struct addrinfo **_addr,int _ai_family){
const struct addrinfo *addr; struct addrinfo *addr;
int err; int err;
addr=*_addr; for(addr=*_addr;;addr=addr->ai_next){
for(;;){
/*Move to the next address of the requested type.*/ /*Move to the next address of the requested type.*/
for(;addr!=NULL&&addr->ai_family!=_ai_family;addr=addr->ai_next); for(;addr!=NULL&&addr->ai_family!=_ai_family;addr=addr->ai_next);
*_addr=addr; *_addr=addr;
@ -1925,7 +2008,6 @@ static int op_sock_connect_next(op_sock _fd,
err=op_errno(); err=op_errno();
/*Winsock will set WSAEWOULDBLOCK.*/ /*Winsock will set WSAEWOULDBLOCK.*/
if(OP_LIKELY(err==EINPROGRESS||err==EWOULDBLOCK))return 0; if(OP_LIKELY(err==EINPROGRESS||err==EWOULDBLOCK))return 0;
addr=addr->ai_next;
} }
} }
@ -1933,15 +2015,15 @@ static int op_sock_connect_next(op_sock _fd,
# define OP_NPROTOS (2) # define OP_NPROTOS (2)
static int op_http_connect_impl(OpusHTTPStream *_stream,OpusHTTPConn *_conn, static int op_http_connect_impl(OpusHTTPStream *_stream,OpusHTTPConn *_conn,
const struct addrinfo *_addrs,struct timeb *_start_time){ struct addrinfo *_addrs,struct timeb *_start_time){
const struct addrinfo *addr; struct addrinfo *addr;
const struct addrinfo *addrs[OP_NPROTOS]; struct addrinfo *addrs[OP_NPROTOS];
struct pollfd fds[OP_NPROTOS]; struct pollfd fds[OP_NPROTOS];
int ai_family; int ai_family;
int nprotos; int nprotos;
int ret; int ret;
int pi; int pi;
int pj; int pj;
for(pi=0;pi<OP_NPROTOS;pi++)addrs[pi]=NULL; for(pi=0;pi<OP_NPROTOS;pi++)addrs[pi]=NULL;
/*Try connecting via both IPv4 and IPv6 simultaneously, and keep the first /*Try connecting via both IPv4 and IPv6 simultaneously, and keep the first
one that succeeds. one that succeeds.
@ -1950,8 +2032,8 @@ static int op_http_connect_impl(OpusHTTPStream *_stream,OpusHTTPConn *_conn,
families were returned in the DNS records in accordance with RFC 6555.*/ families were returned in the DNS records in accordance with RFC 6555.*/
for(addr=_addrs,nprotos=0;addr!=NULL&&nprotos<OP_NPROTOS;addr=addr->ai_next){ for(addr=_addrs,nprotos=0;addr!=NULL&&nprotos<OP_NPROTOS;addr=addr->ai_next){
if(addr->ai_family==AF_INET6||addr->ai_family==AF_INET){ if(addr->ai_family==AF_INET6||addr->ai_family==AF_INET){
OP_ASSERT(addr->ai_addrlen<=sizeof(struct sockaddr_in6)); OP_ASSERT(addr->ai_addrlen<=
OP_ASSERT(addr->ai_addrlen<=sizeof(struct sockaddr_in)); OP_MAX(sizeof(struct sockaddr_in6),sizeof(struct sockaddr_in)));
/*If we've seen this address family before, skip this address for now.*/ /*If we've seen this address family before, skip this address for now.*/
for(pi=0;pi<nprotos;pi++)if(addrs[pi]->ai_family==addr->ai_family)break; for(pi=0;pi<nprotos;pi++)if(addrs[pi]->ai_family==addr->ai_family)break;
if(pi<nprotos)continue; if(pi<nprotos)continue;
@ -2065,7 +2147,7 @@ static int op_http_connect_impl(OpusHTTPStream *_stream,OpusHTTPConn *_conn,
} }
static int op_http_connect(OpusHTTPStream *_stream,OpusHTTPConn *_conn, static int op_http_connect(OpusHTTPStream *_stream,OpusHTTPConn *_conn,
const struct addrinfo *_addrs,struct timeb *_start_time){ struct addrinfo *_addrs,struct timeb *_start_time){
struct timeb resolve_time; struct timeb resolve_time;
struct addrinfo *new_addrs; struct addrinfo *new_addrs;
int ret; int ret;
@ -2138,19 +2220,20 @@ static char *op_base64_encode(char *_dst,const char *_src,int _len){
Scheme and append it to the given string buffer.*/ Scheme and append it to the given string buffer.*/
static int op_sb_append_basic_auth_header(OpusStringBuf *_sb, static int op_sb_append_basic_auth_header(OpusStringBuf *_sb,
const char *_header,const char *_user,const char *_pass){ const char *_header,const char *_user,const char *_pass){
int user_len; size_t user_len;
int pass_len; size_t pass_len;
int user_pass_len; int user_pass_len;
int base64_len; int base64_len;
int nbuf_total; int nbuf_total;
int ret; int ret;
ret=op_sb_append_string(_sb,_header); ret=op_sb_append_string(_sb,_header);
ret|=op_sb_append(_sb,": Basic ",8); ret|=op_sb_append(_sb,": Basic ",8);
user_len=strlen(_user); user_len=strlen(_user);
pass_len=strlen(_pass); pass_len=strlen(_pass);
if(OP_UNLIKELY(user_len>(size_t)INT_MAX))return OP_EFAULT;
if(OP_UNLIKELY(pass_len>INT_MAX-user_len))return OP_EFAULT; if(OP_UNLIKELY(pass_len>INT_MAX-user_len))return OP_EFAULT;
if(OP_UNLIKELY(user_len+pass_len>(INT_MAX>>2)*3-3))return OP_EFAULT; if(OP_UNLIKELY((int)(user_len+pass_len)>(INT_MAX>>2)*3-3))return OP_EFAULT;
user_pass_len=user_len+1+pass_len; user_pass_len=(int)(user_len+pass_len)+1;
base64_len=OP_BASE64_LENGTH(user_pass_len); base64_len=OP_BASE64_LENGTH(user_pass_len);
/*Stick "user:pass" at the end of the buffer so we can Base64 encode it /*Stick "user:pass" at the end of the buffer so we can Base64 encode it
in-place.*/ in-place.*/
@ -2160,9 +2243,9 @@ static int op_sb_append_basic_auth_header(OpusStringBuf *_sb,
ret|=op_sb_ensure_capacity(_sb,nbuf_total); ret|=op_sb_ensure_capacity(_sb,nbuf_total);
if(OP_UNLIKELY(ret<0))return ret; if(OP_UNLIKELY(ret<0))return ret;
_sb->nbuf=nbuf_total-user_pass_len; _sb->nbuf=nbuf_total-user_pass_len;
OP_ALWAYS_TRUE(!op_sb_append(_sb,_user,user_len)); OP_ALWAYS_TRUE(!op_sb_append(_sb,_user,(int)user_len));
OP_ALWAYS_TRUE(!op_sb_append(_sb,":",1)); OP_ALWAYS_TRUE(!op_sb_append(_sb,":",1));
OP_ALWAYS_TRUE(!op_sb_append(_sb,_pass,pass_len)); OP_ALWAYS_TRUE(!op_sb_append(_sb,_pass,(int)pass_len));
op_base64_encode(_sb->buf+nbuf_total-base64_len, op_base64_encode(_sb->buf+nbuf_total-base64_len,
_sb->buf+nbuf_total-user_pass_len,user_pass_len); _sb->buf+nbuf_total-user_pass_len,user_pass_len);
return op_sb_append(_sb,"\r\n",2); return op_sb_append(_sb,"\r\n",2);
@ -2781,7 +2864,7 @@ static int op_http_conn_open_pos(OpusHTTPStream *_stream,
ret=op_http_conn_handle_response(_stream,_conn); ret=op_http_conn_handle_response(_stream,_conn);
if(OP_UNLIKELY(ret!=0))return OP_FALSE; if(OP_UNLIKELY(ret!=0))return OP_FALSE;
ftime(&end_time); ftime(&end_time);
_stream->cur_conni=_conn-_stream->conns; _stream->cur_conni=(int)(_conn-_stream->conns);
OP_ASSERT(_stream->cur_conni>=0&&_stream->cur_conni<OP_NCONNS_MAX); OP_ASSERT(_stream->cur_conni>=0&&_stream->cur_conni<OP_NCONNS_MAX);
/*The connection has been successfully opened. /*The connection has been successfully opened.
Update the connection time estimate.*/ Update the connection time estimate.*/
@ -2885,7 +2968,7 @@ static int op_http_conn_read_body(OpusHTTPStream *_stream,
content_length=_stream->content_length; content_length=_stream->content_length;
} }
OP_ASSERT(end_pos>pos); OP_ASSERT(end_pos>pos);
_buf_size=OP_MIN(_buf_size,end_pos-pos); _buf_size=(int)OP_MIN(_buf_size,end_pos-pos);
} }
nread=op_http_conn_read(_conn,(char *)_buf,_buf_size,1); nread=op_http_conn_read(_conn,(char *)_buf,_buf_size,1);
if(OP_UNLIKELY(nread<0))return nread; if(OP_UNLIKELY(nread<0))return nread;
@ -2921,7 +3004,7 @@ static int op_http_conn_read_body(OpusHTTPStream *_stream,
static int op_http_stream_read(void *_stream, static int op_http_stream_read(void *_stream,
unsigned char *_ptr,int _buf_size){ unsigned char *_ptr,int _buf_size){
OpusHTTPStream *stream; OpusHTTPStream *stream;
ptrdiff_t nread; int nread;
opus_int64 size; opus_int64 size;
opus_int64 pos; opus_int64 pos;
int ci; int ci;
@ -3125,7 +3208,8 @@ static int op_http_stream_seek(void *_stream,opus_int64 _offset,int _whence){
*pnext=conn->next; *pnext=conn->next;
conn->next=stream->lru_head; conn->next=stream->lru_head;
stream->lru_head=conn; stream->lru_head=conn;
stream->cur_conni=conn-stream->conns; stream->cur_conni=(int)(conn-stream->conns);
OP_ASSERT(stream->cur_conni>=0&&stream->cur_conni<OP_NCONNS_MAX);
return 0; return 0;
} }
pnext=&conn->next; pnext=&conn->next;
@ -3177,7 +3261,8 @@ static int op_http_stream_seek(void *_stream,opus_int64 _offset,int _whence){
*pnext=conn->next; *pnext=conn->next;
conn->next=stream->lru_head; conn->next=stream->lru_head;
stream->lru_head=conn; stream->lru_head=conn;
stream->cur_conni=conn-stream->conns; stream->cur_conni=(int)(conn-stream->conns);
OP_ASSERT(stream->cur_conni>=0&&stream->cur_conni<OP_NCONNS_MAX);
return 0; return 0;
} }
close_pnext=pnext; close_pnext=pnext;
@ -3323,7 +3408,7 @@ static void *op_url_stream_create_impl(OpusFileCallbacks *_cb,const char *_url,
*_pinfo will be NULL. *_pinfo will be NULL.
Our caller is responsible for copying *_info to **_pinfo if it ultimately Our caller is responsible for copying *_info to **_pinfo if it ultimately
succeeds, or for clearing *_info if it ultimately fails.*/ succeeds, or for clearing *_info if it ultimately fails.*/
void *op_url_stream_vcreate_impl(OpusFileCallbacks *_cb, static void *op_url_stream_vcreate_impl(OpusFileCallbacks *_cb,
const char *_url,OpusServerInfo *_info,OpusServerInfo **_pinfo,va_list _ap){ const char *_url,OpusServerInfo *_info,OpusServerInfo **_pinfo,va_list _ap){
int skip_certificate_check; int skip_certificate_check;
const char *proxy_host; const char *proxy_host;
@ -3337,6 +3422,7 @@ void *op_url_stream_vcreate_impl(OpusFileCallbacks *_cb,
proxy_user=NULL; proxy_user=NULL;
proxy_pass=NULL; proxy_pass=NULL;
pinfo=NULL; pinfo=NULL;
*_pinfo=NULL;
for(;;){ for(;;){
ptrdiff_t request; ptrdiff_t request;
request=va_arg(_ap,char *)-(char *)NULL; request=va_arg(_ap,char *)-(char *)NULL;
@ -3368,7 +3454,6 @@ void *op_url_stream_vcreate_impl(OpusFileCallbacks *_cb,
} }
/*If the caller has requested server information, proxy it to a local copy to /*If the caller has requested server information, proxy it to a local copy to
simplify error handling.*/ simplify error handling.*/
*_pinfo=NULL;
if(pinfo!=NULL){ if(pinfo!=NULL){
void *ret; void *ret;
opus_server_info_init(_info); opus_server_info_init(_info);
@ -3385,7 +3470,7 @@ void *op_url_stream_vcreate_impl(OpusFileCallbacks *_cb,
void *op_url_stream_vcreate(OpusFileCallbacks *_cb, void *op_url_stream_vcreate(OpusFileCallbacks *_cb,
const char *_url,va_list _ap){ const char *_url,va_list _ap){
OpusServerInfo info; OpusServerInfo info;
OpusServerInfo *pinfo=NULL; OpusServerInfo *pinfo;
void *ret; void *ret;
ret=op_url_stream_vcreate_impl(_cb,_url,&info,&pinfo,_ap); ret=op_url_stream_vcreate_impl(_cb,_url,&info,&pinfo,_ap);
if(pinfo!=NULL)*pinfo=*&info; if(pinfo!=NULL)*pinfo=*&info;

View file

@ -107,26 +107,33 @@ static int op_tags_ensure_capacity(OpusTags *_tags,size_t _ncomments){
char **user_comments; char **user_comments;
int *comment_lengths; int *comment_lengths;
int cur_ncomments; int cur_ncomments;
char *binary_suffix_data;
int binary_suffix_len;
size_t size; size_t size;
if(OP_UNLIKELY(_ncomments>=(size_t)INT_MAX))return OP_EFAULT; if(OP_UNLIKELY(_ncomments>=(size_t)INT_MAX))return OP_EFAULT;
size=sizeof(*_tags->comment_lengths)*(_ncomments+1); size=sizeof(*_tags->comment_lengths)*(_ncomments+1);
if(size/sizeof(*_tags->comment_lengths)!=_ncomments+1)return OP_EFAULT; if(size/sizeof(*_tags->comment_lengths)!=_ncomments+1)return OP_EFAULT;
cur_ncomments=_tags->comments; cur_ncomments=_tags->comments;
/*We only support growing.
Trimming requires cleaning up the allocated strings in the old space, and
is best handled separately if it's ever needed.*/
OP_ASSERT(_ncomments>=(size_t)cur_ncomments);
comment_lengths=_tags->comment_lengths; comment_lengths=_tags->comment_lengths;
binary_suffix_len=comment_lengths==NULL?0:comment_lengths[cur_ncomments];
comment_lengths=(int *)_ogg_realloc(_tags->comment_lengths,size); comment_lengths=(int *)_ogg_realloc(_tags->comment_lengths,size);
if(OP_UNLIKELY(comment_lengths==NULL))return OP_EFAULT; if(OP_UNLIKELY(comment_lengths==NULL))return OP_EFAULT;
comment_lengths[_ncomments]=binary_suffix_len; if(_tags->comment_lengths==NULL){
OP_ASSERT(cur_ncomments==0);
comment_lengths[cur_ncomments]=0;
}
comment_lengths[_ncomments]=comment_lengths[cur_ncomments];
_tags->comment_lengths=comment_lengths; _tags->comment_lengths=comment_lengths;
size=sizeof(*_tags->user_comments)*(_ncomments+1); size=sizeof(*_tags->user_comments)*(_ncomments+1);
if(size/sizeof(*_tags->user_comments)!=_ncomments+1)return OP_EFAULT; if(size/sizeof(*_tags->user_comments)!=_ncomments+1)return OP_EFAULT;
user_comments=_tags->user_comments;
binary_suffix_data=user_comments==NULL?NULL:user_comments[cur_ncomments];
user_comments=(char **)_ogg_realloc(_tags->user_comments,size); user_comments=(char **)_ogg_realloc(_tags->user_comments,size);
if(OP_UNLIKELY(user_comments==NULL))return OP_EFAULT; if(OP_UNLIKELY(user_comments==NULL))return OP_EFAULT;
user_comments[_ncomments]=binary_suffix_data; if(_tags->user_comments==NULL){
OP_ASSERT(cur_ncomments==0);
user_comments[cur_ncomments]=NULL;
}
user_comments[_ncomments]=user_comments[cur_ncomments];
_tags->user_comments=user_comments; _tags->user_comments=user_comments;
return 0; return 0;
} }
@ -279,24 +286,26 @@ int opus_tags_copy(OpusTags *_dst,const OpusTags *_src){
} }
int opus_tags_add(OpusTags *_tags,const char *_tag,const char *_value){ int opus_tags_add(OpusTags *_tags,const char *_tag,const char *_value){
char *comment; char *comment;
int tag_len; size_t tag_len;
int value_len; size_t value_len;
int ncomments; int ncomments;
int ret; int ret;
ncomments=_tags->comments; ncomments=_tags->comments;
ret=op_tags_ensure_capacity(_tags,ncomments+1); ret=op_tags_ensure_capacity(_tags,ncomments+1);
if(OP_UNLIKELY(ret<0))return ret; if(OP_UNLIKELY(ret<0))return ret;
tag_len=strlen(_tag); tag_len=strlen(_tag);
value_len=strlen(_value); value_len=strlen(_value);
/*+2 for '=' and '\0'.*/ /*+2 for '=' and '\0'.*/
if(tag_len+value_len<tag_len)return OP_EFAULT;
if(tag_len+value_len>(size_t)INT_MAX-2)return OP_EFAULT;
comment=(char *)_ogg_malloc(sizeof(*comment)*(tag_len+value_len+2)); comment=(char *)_ogg_malloc(sizeof(*comment)*(tag_len+value_len+2));
if(OP_UNLIKELY(comment==NULL))return OP_EFAULT; if(OP_UNLIKELY(comment==NULL))return OP_EFAULT;
memcpy(comment,_tag,sizeof(*comment)*tag_len); memcpy(comment,_tag,sizeof(*comment)*tag_len);
comment[tag_len]='='; comment[tag_len]='=';
memcpy(comment+tag_len+1,_value,sizeof(*comment)*(value_len+1)); memcpy(comment+tag_len+1,_value,sizeof(*comment)*(value_len+1));
_tags->user_comments[ncomments]=comment; _tags->user_comments[ncomments]=comment;
_tags->comment_lengths[ncomments]=tag_len+value_len+1; _tags->comment_lengths[ncomments]=(int)(tag_len+value_len+1);
_tags->comments=ncomments+1; _tags->comments=ncomments+1;
return 0; return 0;
} }
@ -337,7 +346,10 @@ int opus_tags_set_binary_suffix(OpusTags *_tags,
} }
int opus_tagcompare(const char *_tag_name,const char *_comment){ int opus_tagcompare(const char *_tag_name,const char *_comment){
return opus_tagncompare(_tag_name,strlen(_tag_name),_comment); size_t tag_len;
tag_len=strlen(_tag_name);
if(OP_UNLIKELY(tag_len>(size_t)INT_MAX))return -1;
return opus_tagncompare(_tag_name,(int)tag_len,_comment);
} }
int opus_tagncompare(const char *_tag_name,int _tag_len,const char *_comment){ int opus_tagncompare(const char *_tag_name,int _tag_len,const char *_comment){
@ -348,17 +360,18 @@ int opus_tagncompare(const char *_tag_name,int _tag_len,const char *_comment){
} }
const char *opus_tags_query(const OpusTags *_tags,const char *_tag,int _count){ const char *opus_tags_query(const OpusTags *_tags,const char *_tag,int _count){
char **user_comments; char **user_comments;
int tag_len; size_t tag_len;
int found; int found;
int ncomments; int ncomments;
int ci; int ci;
tag_len=strlen(_tag); tag_len=strlen(_tag);
if(OP_UNLIKELY(tag_len>(size_t)INT_MAX))return NULL;
ncomments=_tags->comments; ncomments=_tags->comments;
user_comments=_tags->user_comments; user_comments=_tags->user_comments;
found=0; found=0;
for(ci=0;ci<ncomments;ci++){ for(ci=0;ci<ncomments;ci++){
if(!opus_tagncompare(_tag,tag_len,user_comments[ci])){ if(!opus_tagncompare(_tag,(int)tag_len,user_comments[ci])){
/*We return a pointer to the data, not a copy.*/ /*We return a pointer to the data, not a copy.*/
if(_count==found++)return user_comments[ci]+tag_len+1; if(_count==found++)return user_comments[ci]+tag_len+1;
} }
@ -368,17 +381,18 @@ const char *opus_tags_query(const OpusTags *_tags,const char *_tag,int _count){
} }
int opus_tags_query_count(const OpusTags *_tags,const char *_tag){ int opus_tags_query_count(const OpusTags *_tags,const char *_tag){
char **user_comments; char **user_comments;
int tag_len; size_t tag_len;
int found; int found;
int ncomments; int ncomments;
int ci; int ci;
tag_len=strlen(_tag); tag_len=strlen(_tag);
if(OP_UNLIKELY(tag_len>(size_t)INT_MAX))return 0;
ncomments=_tags->comments; ncomments=_tags->comments;
user_comments=_tags->user_comments; user_comments=_tags->user_comments;
found=0; found=0;
for(ci=0;ci<ncomments;ci++){ for(ci=0;ci<ncomments;ci++){
if(!opus_tagncompare(_tag,tag_len,user_comments[ci]))found++; if(!opus_tagncompare(_tag,(int)tag_len,user_comments[ci]))found++;
} }
return found; return found;
} }
@ -403,7 +417,8 @@ static int opus_tags_get_gain(const OpusTags *_tags,int *_gain_q8,
ncomments=_tags->comments; ncomments=_tags->comments;
/*Look for the first valid tag with the name _tag_name and use that.*/ /*Look for the first valid tag with the name _tag_name and use that.*/
for(ci=0;ci<ncomments;ci++){ for(ci=0;ci<ncomments;ci++){
if(opus_tagncompare(_tag_name,_tag_len,comments[ci])==0){ OP_ASSERT(_tag_len<=(size_t)INT_MAX);
if(opus_tagncompare(_tag_name,(int)_tag_len,comments[ci])==0){
char *p; char *p;
opus_int32 gain_q8; opus_int32 gain_q8;
int negative; int negative;

View file

@ -136,6 +136,9 @@ struct OggOpusLink{
that end-trimming calculations work properly. that end-trimming calculations work properly.
This is only valid for seekable sources.*/ This is only valid for seekable sources.*/
opus_int64 end_offset; opus_int64 end_offset;
/*The total duration of all prior links.
This is always zero for non-seekable sources.*/
ogg_int64_t pcm_file_offset;
/*The granule position of the last sample. /*The granule position of the last sample.
This is only valid for seekable sources.*/ This is only valid for seekable sources.*/
ogg_int64_t pcm_end; ogg_int64_t pcm_end;
@ -150,23 +153,25 @@ struct OggOpusLink{
}; };
struct OggOpusFile{ struct OggOpusFile{
/*The callbacks used to access the data source.*/ /*The callbacks used to access the stream.*/
OpusFileCallbacks callbacks; OpusFileCallbacks callbacks;
/*A FILE *, memory bufer, etc.*/ /*A FILE *, memory buffer, etc.*/
void *source; void *stream;
/*Whether or not we can seek with this data source.*/ /*Whether or not we can seek with this stream.*/
int seekable; int seekable;
/*The number of links in this chained Ogg Opus file.*/ /*The number of links in this chained Ogg Opus file.*/
int nlinks; int nlinks;
/*The cached information from each link in a chained Ogg Opus file. /*The cached information from each link in a chained Ogg Opus file.
If source isn't seekable (e.g., it's a pipe), only the current link If stream isn't seekable (e.g., it's a pipe), only the current link
appears.*/ appears.*/
OggOpusLink *links; OggOpusLink *links;
/*The number of serial numbers from a single link.*/ /*The number of serial numbers from a single link.*/
int nserialnos; int nserialnos;
/*The capacity of the list of serial numbers from a single link.*/ /*The capacity of the list of serial numbers from a single link.*/
int cserialnos; int cserialnos;
/*Storage for the list of serial numbers from a single link.*/ /*Storage for the list of serial numbers from a single link.
This is a scratch buffer used when scanning the BOS pages at the start of
each link.*/
ogg_uint32_t *serialnos; ogg_uint32_t *serialnos;
/*This is the current offset of the data processed by the ogg_sync_state. /*This is the current offset of the data processed by the ogg_sync_state.
After a seek, this should be set to the target offset so that we can track After a seek, this should be set to the target offset so that we can track
@ -174,9 +179,9 @@ struct OggOpusFile{
After a call to op_get_next_page(), this will point to the first byte after After a call to op_get_next_page(), this will point to the first byte after
that page.*/ that page.*/
opus_int64 offset; opus_int64 offset;
/*The total size of this data source, or -1 if it's unseekable.*/ /*The total size of this stream, or -1 if it's unseekable.*/
opus_int64 end; opus_int64 end;
/*Used to locate pages in the data source.*/ /*Used to locate pages in the stream.*/
ogg_sync_state oy; ogg_sync_state oy;
/*One of OP_NOTOPEN, OP_PARTOPEN, OP_OPENED, OP_STREAMSET, OP_INITSET.*/ /*One of OP_NOTOPEN, OP_PARTOPEN, OP_OPENED, OP_STREAMSET, OP_INITSET.*/
int ready_state; int ready_state;
@ -227,7 +232,7 @@ struct OggOpusFile{
/*The number of valid samples in the decoded buffer.*/ /*The number of valid samples in the decoded buffer.*/
int od_buffer_size; int od_buffer_size;
/*The type of gain offset to apply. /*The type of gain offset to apply.
One of OP_HEADER_GAIN, OP_TRACK_GAIN, or OP_ABSOLUTE_GAIN.*/ One of OP_HEADER_GAIN, OP_ALBUM_GAIN, OP_TRACK_GAIN, or OP_ABSOLUTE_GAIN.*/
int gain_type; int gain_type;
/*The offset to apply to the gain.*/ /*The offset to apply to the gain.*/
opus_int32 gain_offset_q8; opus_int32 gain_offset_q8;

View file

@ -86,14 +86,15 @@ int op_test(OpusHead *_head,
This is to prevent us spending a lot of time allocating memory and looking This is to prevent us spending a lot of time allocating memory and looking
for Ogg pages in non-Ogg files.*/ for Ogg pages in non-Ogg files.*/
if(memcmp(_initial_data,"OggS",4)!=0)return OP_ENOTFORMAT; if(memcmp(_initial_data,"OggS",4)!=0)return OP_ENOTFORMAT;
if(OP_UNLIKELY(_initial_bytes>(size_t)LONG_MAX))return OP_EFAULT;
ogg_sync_init(&oy); ogg_sync_init(&oy);
data=ogg_sync_buffer(&oy,_initial_bytes); data=ogg_sync_buffer(&oy,(long)_initial_bytes);
if(data!=NULL){ if(data!=NULL){
ogg_stream_state os; ogg_stream_state os;
ogg_page og; ogg_page og;
int ret; int ret;
memcpy(data,_initial_data,_initial_bytes); memcpy(data,_initial_data,_initial_bytes);
ogg_sync_wrote(&oy,_initial_bytes); ogg_sync_wrote(&oy,(long)_initial_bytes);
ogg_stream_init(&os,-1); ogg_stream_init(&os,-1);
err=OP_FALSE; err=OP_FALSE;
do{ do{
@ -147,7 +148,7 @@ static int op_get_data(OggOpusFile *_of,int _nbytes){
int nbytes; int nbytes;
OP_ASSERT(_nbytes>0); OP_ASSERT(_nbytes>0);
buffer=(unsigned char *)ogg_sync_buffer(&_of->oy,_nbytes); buffer=(unsigned char *)ogg_sync_buffer(&_of->oy,_nbytes);
nbytes=(int)(*_of->callbacks.read)(_of->source,buffer,_nbytes); nbytes=(int)(*_of->callbacks.read)(_of->stream,buffer,_nbytes);
OP_ASSERT(nbytes<=_nbytes); OP_ASSERT(nbytes<=_nbytes);
if(OP_LIKELY(nbytes>0))ogg_sync_wrote(&_of->oy,nbytes); if(OP_LIKELY(nbytes>0))ogg_sync_wrote(&_of->oy,nbytes);
return nbytes; return nbytes;
@ -157,7 +158,7 @@ static int op_get_data(OggOpusFile *_of,int _nbytes){
static int op_seek_helper(OggOpusFile *_of,opus_int64 _offset){ static int op_seek_helper(OggOpusFile *_of,opus_int64 _offset){
if(_offset==_of->offset)return 0; if(_offset==_of->offset)return 0;
if(_of->callbacks.seek==NULL if(_of->callbacks.seek==NULL
||(*_of->callbacks.seek)(_of->source,_offset,SEEK_SET)){ ||(*_of->callbacks.seek)(_of->stream,_offset,SEEK_SET)){
return OP_EREAD; return OP_EREAD;
} }
_of->offset=_offset; _of->offset=_offset;
@ -165,7 +166,7 @@ static int op_seek_helper(OggOpusFile *_of,opus_int64 _offset){
return 0; return 0;
} }
/*Get the current position indicator of the underlying source. /*Get the current position indicator of the underlying stream.
This should be the same as the value reported by tell().*/ This should be the same as the value reported by tell().*/
static opus_int64 op_position(const OggOpusFile *_of){ static opus_int64 op_position(const OggOpusFile *_of){
/*The current position indicator is _not_ simply offset. /*The current position indicator is _not_ simply offset.
@ -369,7 +370,7 @@ static int op_get_prev_page_serial(OggOpusFile *_of,OpusSeekRecord *_sr,
search_start=llret+1; search_start=llret+1;
} }
/*We started from the beginning of the stream and found nothing. /*We started from the beginning of the stream and found nothing.
This should be impossible unless the contents of the source changed out This should be impossible unless the contents of the stream changed out
from under us after we read from it.*/ from under us after we read from it.*/
if(OP_UNLIKELY(!begin)&&OP_UNLIKELY(_offset<0))return OP_EBADLINK; if(OP_UNLIKELY(!begin)&&OP_UNLIKELY(_offset<0))return OP_EBADLINK;
/*Bump up the chunk size. /*Bump up the chunk size.
@ -455,7 +456,7 @@ static opus_int64 op_get_last_page(OggOpusFile *_of,ogg_int64_t *_gp,
} }
} }
/*We started from at or before the beginning of the link and found nothing. /*We started from at or before the beginning of the link and found nothing.
This should be impossible unless the contents of the source changed out This should be impossible unless the contents of the stream changed out
from under us after we read from it.*/ from under us after we read from it.*/
if((OP_UNLIKELY(left_link)||OP_UNLIKELY(!begin))&&OP_UNLIKELY(_offset<0)){ if((OP_UNLIKELY(left_link)||OP_UNLIKELY(!begin))&&OP_UNLIKELY(_offset<0)){
return OP_EBADLINK; return OP_EBADLINK;
@ -855,6 +856,7 @@ static int op_find_initial_pcm_offset(OggOpusFile *_of,
/*Fail if the pre-skip is non-zero, since it's asking us to skip more /*Fail if the pre-skip is non-zero, since it's asking us to skip more
samples than exist.*/ samples than exist.*/
if(_link->head.pre_skip>0)return OP_EBADTIMESTAMP; if(_link->head.pre_skip>0)return OP_EBADTIMESTAMP;
_link->pcm_file_offset=0;
/*Set pcm_end and end_offset so we can skip the call to /*Set pcm_end and end_offset so we can skip the call to
op_find_final_pcm_offset().*/ op_find_final_pcm_offset().*/
_link->pcm_start=_link->pcm_end=0; _link->pcm_start=_link->pcm_end=0;
@ -866,7 +868,8 @@ static int op_find_initial_pcm_offset(OggOpusFile *_of,
if(_link->head.pre_skip>0)return OP_EBADTIMESTAMP; if(_link->head.pre_skip>0)return OP_EBADTIMESTAMP;
/*Set pcm_end and end_offset so we can skip the call to /*Set pcm_end and end_offset so we can skip the call to
op_find_final_pcm_offset().*/ op_find_final_pcm_offset().*/
_link->pcm_end=_link->pcm_start=0; _link->pcm_file_offset=0;
_link->pcm_start=_link->pcm_end=0;
_link->end_offset=_link->data_offset; _link->end_offset=_link->data_offset;
/*Tell the caller we've got a buffered page for them.*/ /*Tell the caller we've got a buffered page for them.*/
return 1; return 1;
@ -951,6 +954,7 @@ static int op_find_initial_pcm_offset(OggOpusFile *_of,
/*Update the packet count after end-trimming.*/ /*Update the packet count after end-trimming.*/
_of->op_count=pi; _of->op_count=pi;
_of->cur_discard_count=_link->head.pre_skip; _of->cur_discard_count=_link->head.pre_skip;
_link->pcm_file_offset=0;
_of->prev_packet_gp=_link->pcm_start=pcm_start; _of->prev_packet_gp=_link->pcm_start=pcm_start;
_of->prev_page_offset=page_offset; _of->prev_page_offset=page_offset;
return 0; return 0;
@ -1271,6 +1275,7 @@ static int op_bisect_forward_serialno(OggOpusFile *_of,
always starts with a seek.*/ always starts with a seek.*/
ret=op_find_initial_pcm_offset(_of,links+nlinks,NULL); ret=op_find_initial_pcm_offset(_of,links+nlinks,NULL);
if(OP_UNLIKELY(ret<0))return ret; if(OP_UNLIKELY(ret<0))return ret;
links[nlinks].pcm_file_offset=total_duration;
_searched=_of->offset; _searched=_of->offset;
/*Mark the current link count so it can be cleaned up on error.*/ /*Mark the current link count so it can be cleaned up on error.*/
_of->nlinks=++nlinks; _of->nlinks=++nlinks;
@ -1390,8 +1395,8 @@ static int op_open_seekable2_impl(OggOpusFile *_of){
opus_int64 data_offset; opus_int64 data_offset;
int ret; int ret;
/*We can seek, so set out learning all about this file.*/ /*We can seek, so set out learning all about this file.*/
(*_of->callbacks.seek)(_of->source,0,SEEK_END); (*_of->callbacks.seek)(_of->stream,0,SEEK_END);
_of->offset=_of->end=(*_of->callbacks.tell)(_of->source); _of->offset=_of->end=(*_of->callbacks.tell)(_of->stream);
if(OP_UNLIKELY(_of->end<0))return OP_EREAD; if(OP_UNLIKELY(_of->end<0))return OP_EREAD;
data_offset=_of->links[0].data_offset; data_offset=_of->links[0].data_offset;
if(OP_UNLIKELY(_of->end<data_offset))return OP_EBADLINK; if(OP_UNLIKELY(_of->end<data_offset))return OP_EBADLINK;
@ -1436,7 +1441,7 @@ static int op_open_seekable2(OggOpusFile *_of){
prev_page_offset=_of->prev_page_offset; prev_page_offset=_of->prev_page_offset;
start_offset=_of->offset; start_offset=_of->offset;
memcpy(op_start,_of->op,sizeof(*op_start)*start_op_count); memcpy(op_start,_of->op,sizeof(*op_start)*start_op_count);
OP_ASSERT((*_of->callbacks.tell)(_of->source)==op_position(_of)); OP_ASSERT((*_of->callbacks.tell)(_of->stream)==op_position(_of));
ogg_sync_init(&_of->oy); ogg_sync_init(&_of->oy);
ogg_stream_init(&_of->os,-1); ogg_stream_init(&_of->os,-1);
ret=op_open_seekable2_impl(_of); ret=op_open_seekable2_impl(_of);
@ -1454,7 +1459,7 @@ static int op_open_seekable2(OggOpusFile *_of){
_of->cur_discard_count=_of->links[0].head.pre_skip; _of->cur_discard_count=_of->links[0].head.pre_skip;
if(OP_UNLIKELY(ret<0))return ret; if(OP_UNLIKELY(ret<0))return ret;
/*And restore the position indicator.*/ /*And restore the position indicator.*/
ret=(*_of->callbacks.seek)(_of->source,op_position(_of),SEEK_SET); ret=(*_of->callbacks.seek)(_of->stream,op_position(_of),SEEK_SET);
return OP_UNLIKELY(ret<0)?OP_EREAD:0; return OP_UNLIKELY(ret<0)?OP_EREAD:0;
} }
@ -1493,19 +1498,20 @@ static void op_clear(OggOpusFile *_of){
_ogg_free(_of->serialnos); _ogg_free(_of->serialnos);
ogg_stream_clear(&_of->os); ogg_stream_clear(&_of->os);
ogg_sync_clear(&_of->oy); ogg_sync_clear(&_of->oy);
if(_of->callbacks.close!=NULL)(*_of->callbacks.close)(_of->source); if(_of->callbacks.close!=NULL)(*_of->callbacks.close)(_of->stream);
} }
static int op_open1(OggOpusFile *_of, static int op_open1(OggOpusFile *_of,
void *_source,const OpusFileCallbacks *_cb, void *_stream,const OpusFileCallbacks *_cb,
const unsigned char *_initial_data,size_t _initial_bytes){ const unsigned char *_initial_data,size_t _initial_bytes){
ogg_page og; ogg_page og;
ogg_page *pog; ogg_page *pog;
int seekable; int seekable;
int ret; int ret;
memset(_of,0,sizeof(*_of)); memset(_of,0,sizeof(*_of));
if(OP_UNLIKELY(_initial_bytes>(size_t)LONG_MAX))return OP_EFAULT;
_of->end=-1; _of->end=-1;
_of->source=_source; _of->stream=_stream;
*&_of->callbacks=*_cb; *&_of->callbacks=*_cb;
/*At a minimum, we need to be able to read data.*/ /*At a minimum, we need to be able to read data.*/
if(OP_UNLIKELY(_of->callbacks.read==NULL))return OP_EREAD; if(OP_UNLIKELY(_of->callbacks.read==NULL))return OP_EREAD;
@ -1520,18 +1526,18 @@ static int op_open1(OggOpusFile *_of,
decoding entire files from RAM.*/ decoding entire files from RAM.*/
if(_initial_bytes>0){ if(_initial_bytes>0){
char *buffer; char *buffer;
buffer=ogg_sync_buffer(&_of->oy,_initial_bytes); buffer=ogg_sync_buffer(&_of->oy,(long)_initial_bytes);
memcpy(buffer,_initial_data,_initial_bytes*sizeof(*buffer)); memcpy(buffer,_initial_data,_initial_bytes*sizeof(*buffer));
ogg_sync_wrote(&_of->oy,_initial_bytes); ogg_sync_wrote(&_of->oy,(long)_initial_bytes);
} }
/*Can we seek? /*Can we seek?
Stevens suggests the seek test is portable.*/ Stevens suggests the seek test is portable.*/
seekable=_cb->seek!=NULL&&(*_cb->seek)(_source,0,SEEK_CUR)!=-1; seekable=_cb->seek!=NULL&&(*_cb->seek)(_stream,0,SEEK_CUR)!=-1;
/*If seek is implemented, tell must also be implemented.*/ /*If seek is implemented, tell must also be implemented.*/
if(seekable){ if(seekable){
opus_int64 pos; opus_int64 pos;
if(OP_UNLIKELY(_of->callbacks.tell==NULL))return OP_EINVAL; if(OP_UNLIKELY(_of->callbacks.tell==NULL))return OP_EINVAL;
pos=(*_of->callbacks.tell)(_of->source); pos=(*_of->callbacks.tell)(_of->stream);
/*If the current position is not equal to the initial bytes consumed, /*If the current position is not equal to the initial bytes consumed,
absolute seeking will not work.*/ absolute seeking will not work.*/
if(OP_UNLIKELY(pos!=(opus_int64)_initial_bytes))return OP_EINVAL; if(OP_UNLIKELY(pos!=(opus_int64)_initial_bytes))return OP_EINVAL;
@ -1590,14 +1596,14 @@ static int op_open2(OggOpusFile *_of){
return ret; return ret;
} }
OggOpusFile *op_test_callbacks(void *_source,const OpusFileCallbacks *_cb, OggOpusFile *op_test_callbacks(void *_stream,const OpusFileCallbacks *_cb,
const unsigned char *_initial_data,size_t _initial_bytes,int *_error){ const unsigned char *_initial_data,size_t _initial_bytes,int *_error){
OggOpusFile *of; OggOpusFile *of;
int ret; int ret;
of=(OggOpusFile *)_ogg_malloc(sizeof(*of)); of=(OggOpusFile *)_ogg_malloc(sizeof(*of));
ret=OP_EFAULT; ret=OP_EFAULT;
if(OP_LIKELY(of!=NULL)){ if(OP_LIKELY(of!=NULL)){
ret=op_open1(of,_source,_cb,_initial_data,_initial_bytes); ret=op_open1(of,_stream,_cb,_initial_data,_initial_bytes);
if(OP_LIKELY(ret>=0)){ if(OP_LIKELY(ret>=0)){
if(_error!=NULL)*_error=0; if(_error!=NULL)*_error=0;
return of; return of;
@ -1611,10 +1617,10 @@ OggOpusFile *op_test_callbacks(void *_source,const OpusFileCallbacks *_cb,
return NULL; return NULL;
} }
OggOpusFile *op_open_callbacks(void *_source,const OpusFileCallbacks *_cb, OggOpusFile *op_open_callbacks(void *_stream,const OpusFileCallbacks *_cb,
const unsigned char *_initial_data,size_t _initial_bytes,int *_error){ const unsigned char *_initial_data,size_t _initial_bytes,int *_error){
OggOpusFile *of; OggOpusFile *of;
of=op_test_callbacks(_source,_cb,_initial_data,_initial_bytes,_error); of=op_test_callbacks(_stream,_cb,_initial_data,_initial_bytes,_error);
if(OP_LIKELY(of!=NULL)){ if(OP_LIKELY(of!=NULL)){
int ret; int ret;
ret=op_open2(of); ret=op_open2(of);
@ -1627,15 +1633,15 @@ OggOpusFile *op_open_callbacks(void *_source,const OpusFileCallbacks *_cb,
/*Convenience routine to clean up from failure for the open functions that /*Convenience routine to clean up from failure for the open functions that
create their own streams.*/ create their own streams.*/
static OggOpusFile *op_open_close_on_failure(void *_source, static OggOpusFile *op_open_close_on_failure(void *_stream,
const OpusFileCallbacks *_cb,int *_error){ const OpusFileCallbacks *_cb,int *_error){
OggOpusFile *of; OggOpusFile *of;
if(OP_UNLIKELY(_source==NULL)){ if(OP_UNLIKELY(_stream==NULL)){
if(_error!=NULL)*_error=OP_EFAULT; if(_error!=NULL)*_error=OP_EFAULT;
return NULL; return NULL;
} }
of=op_open_callbacks(_source,_cb,NULL,0,_error); of=op_open_callbacks(_stream,_cb,NULL,0,_error);
if(OP_UNLIKELY(of==NULL))(*_cb->close)(_source); if(OP_UNLIKELY(of==NULL))(*_cb->close)(_stream);
return of; return of;
} }
@ -1653,15 +1659,15 @@ OggOpusFile *op_open_memory(const unsigned char *_data,size_t _size,
/*Convenience routine to clean up from failure for the open functions that /*Convenience routine to clean up from failure for the open functions that
create their own streams.*/ create their own streams.*/
static OggOpusFile *op_test_close_on_failure(void *_source, static OggOpusFile *op_test_close_on_failure(void *_stream,
const OpusFileCallbacks *_cb,int *_error){ const OpusFileCallbacks *_cb,int *_error){
OggOpusFile *of; OggOpusFile *of;
if(OP_UNLIKELY(_source==NULL)){ if(OP_UNLIKELY(_stream==NULL)){
if(_error!=NULL)*_error=OP_EFAULT; if(_error!=NULL)*_error=OP_EFAULT;
return NULL; return NULL;
} }
of=op_test_callbacks(_source,_cb,NULL,0,_error); of=op_test_callbacks(_stream,_cb,NULL,0,_error);
if(OP_UNLIKELY(of==NULL))(*_cb->close)(_source); if(OP_UNLIKELY(of==NULL))(*_cb->close)(_stream);
return of; return of;
} }
@ -1702,7 +1708,7 @@ int op_link_count(const OggOpusFile *_of){
return _of->nlinks; return _of->nlinks;
} }
ogg_uint32_t op_serialno(const OggOpusFile *_of,int _li){ opus_uint32 op_serialno(const OggOpusFile *_of,int _li){
if(OP_UNLIKELY(_li>=_of->nlinks))_li=_of->nlinks-1; if(OP_UNLIKELY(_li>=_of->nlinks))_li=_of->nlinks-1;
if(!_of->seekable)_li=0; if(!_of->seekable)_li=0;
return _of->links[_li<0?_of->cur_link:_li].serialno; return _of->links[_li<0?_of->cur_link:_li].serialno;
@ -1718,13 +1724,14 @@ opus_int64 op_raw_total(const OggOpusFile *_of,int _li){
||OP_UNLIKELY(_li>=_of->nlinks)){ ||OP_UNLIKELY(_li>=_of->nlinks)){
return OP_EINVAL; return OP_EINVAL;
} }
if(_li<0)return _of->end-_of->links[0].offset; if(_li<0)return _of->end;
return (_li+1>=_of->nlinks?_of->end:_of->links[_li+1].offset) return (_li+1>=_of->nlinks?_of->end:_of->links[_li+1].offset)
-_of->links[_li].offset; -(_li>0?_of->links[_li].offset:0);
} }
ogg_int64_t op_pcm_total(const OggOpusFile *_of,int _li){ ogg_int64_t op_pcm_total(const OggOpusFile *_of,int _li){
OggOpusLink *links; OggOpusLink *links;
ogg_int64_t pcm_total;
ogg_int64_t diff=0; ogg_int64_t diff=0;
int nlinks; int nlinks;
nlinks=_of->nlinks; nlinks=_of->nlinks;
@ -1737,20 +1744,14 @@ ogg_int64_t op_pcm_total(const OggOpusFile *_of,int _li){
/*We verify that the granule position differences are larger than the /*We verify that the granule position differences are larger than the
pre-skip and that the total duration does not overflow during link pre-skip and that the total duration does not overflow during link
enumeration, so we don't have to check here.*/ enumeration, so we don't have to check here.*/
pcm_total=0;
if(_li<0){ if(_li<0){
ogg_int64_t pcm_total; pcm_total=links[nlinks-1].pcm_file_offset;
int li; _li=nlinks-1;
pcm_total=0;
for(li=0;li<nlinks;li++){
OP_ALWAYS_TRUE(!op_granpos_diff(&diff,
links[li].pcm_end,links[li].pcm_start));
pcm_total+=diff-links[li].head.pre_skip;
}
return pcm_total;
} }
OP_ALWAYS_TRUE(!op_granpos_diff(&diff, OP_ALWAYS_TRUE(!op_granpos_diff(&diff,
links[_li].pcm_end,links[_li].pcm_start)); links[_li].pcm_end,links[_li].pcm_start));
return diff-links[_li].head.pre_skip; return pcm_total+diff-links[_li].head.pre_skip;
} }
const OpusHead *op_head(const OggOpusFile *_of,int _li){ const OpusHead *op_head(const OggOpusFile *_of,int _li){
@ -1820,6 +1821,34 @@ opus_int32 op_bitrate_instant(OggOpusFile *_of){
return ret; return ret;
} }
/*Given a serialno, find a link with a corresponding Opus stream, if it exists.
Return: The index of the link to which the page belongs, or a negative number
if it was not a desired Opus bitstream section.*/
static int op_get_link_from_serialno(const OggOpusFile *_of,int _cur_link,
opus_int64 _page_offset,ogg_uint32_t _serialno){
const OggOpusLink *links;
int nlinks;
int li_lo;
int li_hi;
OP_ASSERT(_of->seekable);
links=_of->links;
nlinks=_of->nlinks;
li_lo=0;
/*Start off by guessing we're just a multiplexed page in the current link.*/
li_hi=_cur_link+1<nlinks&&_page_offset<links[nlinks+1].offset?
_cur_link+1:nlinks;
do{
if(_page_offset>=links[_cur_link].offset)li_lo=_cur_link;
else li_hi=_cur_link;
_cur_link=li_lo+(li_hi-li_lo>>1);
}
while(li_hi-li_lo>1);
/*We've identified the link that should contain this page.
Make sure it's a page we care about.*/
if(links[_cur_link].serialno!=_serialno)return OP_FALSE;
return _cur_link;
}
/*Fetch and process a page. /*Fetch and process a page.
This handles the case where we're at a bitstream boundary and dumps the This handles the case where we're at a bitstream boundary and dumps the
decoding machine. decoding machine.
@ -1876,19 +1905,28 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
if(OP_UNLIKELY(_of->ready_state<OP_STREAMSET)){ if(OP_UNLIKELY(_of->ready_state<OP_STREAMSET)){
if(seekable){ if(seekable){
ogg_uint32_t serialno; ogg_uint32_t serialno;
int nlinks;
int li;
serialno=ogg_page_serialno(&og); serialno=ogg_page_serialno(&og);
/*Match the serialno to bitstream section. /*Match the serialno to bitstream section.*/
We use this rather than offset positions to avoid problems near OP_ASSERT(cur_link>=0&&cur_link<_of->nlinks);
logical bitstream boundaries.*/ if(links[cur_link].serialno!=serialno){
nlinks=_of->nlinks; /*It wasn't a page from the current link.
for(li=0;li<nlinks&&links[li].serialno!=serialno;li++); Is it from the next one?*/
/*Not a desired Opus bitstream section. if(OP_LIKELY(cur_link+1<_of->nlinks&&links[cur_link+1].serialno==
Keep trying.*/ serialno)){
if(li>=nlinks)continue; cur_link++;
}
else{
int new_link;
new_link=
op_get_link_from_serialno(_of,cur_link,_page_offset,serialno);
/*Not a desired Opus bitstream section.
Keep trying.*/
if(new_link<0)continue;
cur_link=new_link;
}
}
cur_serialno=serialno; cur_serialno=serialno;
_of->cur_link=cur_link=li; _of->cur_link=cur_link;
ogg_stream_reset_serialno(&_of->os,serialno); ogg_stream_reset_serialno(&_of->os,serialno);
_of->ready_state=OP_STREAMSET; _of->ready_state=OP_STREAMSET;
/*If we're at the start of this link, initialize the granule position /*If we're at the start of this link, initialize the granule position
@ -1942,13 +1980,32 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
opus_int32 total_duration; opus_int32 total_duration;
int durations[255]; int durations[255];
int op_count; int op_count;
int report_hole;
report_hole=0;
total_duration=op_collect_audio_packets(_of,durations); total_duration=op_collect_audio_packets(_of,durations);
if(OP_UNLIKELY(total_duration<0)){ if(OP_UNLIKELY(total_duration<0)){
/*Drain the packets from the page anyway.*/ /*libogg reported a hole (a gap in the page sequence numbers).
Drain the packets from the page anyway.
If we don't, they'll still be there when we fetch the next page.
Then, when we go to pull out packets, we might get more than 255,
which would overrun our packet buffer.*/
total_duration=op_collect_audio_packets(_of,durations); total_duration=op_collect_audio_packets(_of,durations);
OP_ASSERT(total_duration>=0); OP_ASSERT(total_duration>=0);
/*Report holes to the caller.*/ if(!_ignore_holes){
if(!_ignore_holes)return OP_HOLE; /*Report the hole to the caller after we finish timestamping the
packets.*/
report_hole=1;
/*We had lost or damaged pages, so reset our granule position
tracking.
This makes holes behave the same as a small raw seek.
If the next page is the EOS page, we'll discard it (because we
can't perform end trimming properly), and we'll always discard at
least 80 ms of audio (to allow decoder state to re-converge).
We could try to fill in the gap with PLC by looking at timestamps
in the non-EOS case, but that's complicated and error prone and we
can't rely on the timestamps being valid.*/
_of->prev_packet_gp=-1;
}
} }
op_count=_of->op_count; op_count=_of->op_count;
/*If we found at least one audio data packet, compute per-packet granule /*If we found at least one audio data packet, compute per-packet granule
@ -1975,6 +2032,7 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
Proceed to the next link, rather than risk playing back some Proceed to the next link, rather than risk playing back some
samples that shouldn't have been played.*/ samples that shouldn't have been played.*/
_of->op_count=0; _of->op_count=0;
if(report_hole)return OP_HOLE;
continue; continue;
} }
/*By default discard 80 ms of data after a seek, unless we seek /*By default discard 80 ms of data after a seek, unless we seek
@ -2076,10 +2134,11 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
} }
_of->prev_packet_gp=prev_packet_gp; _of->prev_packet_gp=prev_packet_gp;
_of->prev_page_offset=_page_offset; _of->prev_page_offset=_page_offset;
_of->op_count=pi; _of->op_count=op_count=pi;
/*If end-trimming didn't trim all the packets, we're done.*/
if(OP_LIKELY(pi>0))return 0;
} }
if(report_hole)return OP_HOLE;
/*If end-trimming didn't trim all the packets, we're done.*/
if(op_count>0)return 0;
} }
} }
} }
@ -2117,35 +2176,41 @@ static ogg_int64_t op_get_granulepos(const OggOpusFile *_of,
ogg_int64_t _pcm_offset,int *_li){ ogg_int64_t _pcm_offset,int *_li){
const OggOpusLink *links; const OggOpusLink *links;
ogg_int64_t duration=0; ogg_int64_t duration=0;
ogg_int64_t pcm_start;
opus_int32 pre_skip;
int nlinks; int nlinks;
int li; int li_lo;
int li_hi;
OP_ASSERT(_pcm_offset>=0); OP_ASSERT(_pcm_offset>=0);
nlinks=_of->nlinks; nlinks=_of->nlinks;
links=_of->links; links=_of->links;
for(li=0;OP_LIKELY(li<nlinks);li++){ li_lo=0;
ogg_int64_t pcm_start; li_hi=nlinks;
opus_int32 pre_skip; do{
pcm_start=links[li].pcm_start; int li;
pre_skip=links[li].head.pre_skip; li=li_lo+(li_hi-li_lo>>1);
OP_ALWAYS_TRUE(!op_granpos_diff(&duration,links[li].pcm_end,pcm_start)); if(links[li].pcm_file_offset<=_pcm_offset)li_lo=li;
duration-=pre_skip; else li_hi=li;
if(_pcm_offset<duration){
_pcm_offset+=pre_skip;
if(OP_UNLIKELY(pcm_start>OP_INT64_MAX-_pcm_offset)){
/*Adding this amount to the granule position would overflow the positive
half of its 64-bit range.
Since signed overflow is undefined in C, do it in a way the compiler
isn't allowed to screw up.*/
_pcm_offset-=OP_INT64_MAX-pcm_start+1;
pcm_start=OP_INT64_MIN;
}
pcm_start+=_pcm_offset;
*_li=li;
return pcm_start;
}
_pcm_offset-=duration;
} }
return -1; while(li_hi-li_lo>1);
_pcm_offset-=links[li_lo].pcm_file_offset;
pcm_start=links[li_lo].pcm_start;
pre_skip=links[li_lo].head.pre_skip;
OP_ALWAYS_TRUE(!op_granpos_diff(&duration,links[li_lo].pcm_end,pcm_start));
duration-=pre_skip;
if(_pcm_offset>=duration)return -1;
_pcm_offset+=pre_skip;
if(OP_UNLIKELY(pcm_start>OP_INT64_MAX-_pcm_offset)){
/*Adding this amount to the granule position would overflow the positive
half of its 64-bit range.
Since signed overflow is undefined in C, do it in a way the compiler
isn't allowed to screw up.*/
_pcm_offset-=OP_INT64_MAX-pcm_start+1;
pcm_start=OP_INT64_MIN;
}
pcm_start+=_pcm_offset;
*_li=li_lo;
return pcm_start;
} }
/*A small helper to determine if an Ogg page contains data that continues onto /*A small helper to determine if an Ogg page contains data that continues onto
@ -2193,7 +2258,7 @@ static int op_pcm_seek_page(OggOpusFile *_of,
ogg_int64_t pcm_start; ogg_int64_t pcm_start;
ogg_int64_t pcm_end; ogg_int64_t pcm_end;
ogg_int64_t best_gp; ogg_int64_t best_gp;
ogg_int64_t diff=0; ogg_int64_t diff;
ogg_uint32_t serialno; ogg_uint32_t serialno;
opus_int32 pre_skip; opus_int32 pre_skip;
opus_int64 begin; opus_int64 begin;
@ -2331,7 +2396,7 @@ static int op_pcm_seek_page(OggOpusFile *_of,
d2=end-begin>>1; d2=end-begin>>1;
if(force_bisect)bisect=begin+(end-begin>>1); if(force_bisect)bisect=begin+(end-begin>>1);
else{ else{
ogg_int64_t diff2=0; ogg_int64_t diff2;
OP_ALWAYS_TRUE(!op_granpos_diff(&diff,_target_gp,pcm_start)); OP_ALWAYS_TRUE(!op_granpos_diff(&diff,_target_gp,pcm_start));
OP_ALWAYS_TRUE(!op_granpos_diff(&diff2,pcm_end,pcm_start)); OP_ALWAYS_TRUE(!op_granpos_diff(&diff2,pcm_end,pcm_start));
/*Take a (pretty decent) guess.*/ /*Take a (pretty decent) guess.*/
@ -2606,22 +2671,14 @@ static ogg_int64_t op_get_pcm_offset(const OggOpusFile *_of,
ogg_int64_t _gp,int _li){ ogg_int64_t _gp,int _li){
const OggOpusLink *links; const OggOpusLink *links;
ogg_int64_t pcm_offset; ogg_int64_t pcm_offset;
ogg_int64_t delta=0;
int li;
links=_of->links; links=_of->links;
pcm_offset=0; OP_ASSERT(_li>=0&&_li<_of->nlinks);
OP_ASSERT(_li<_of->nlinks); pcm_offset=links[_li].pcm_file_offset;
for(li=0;li<_li;li++){
OP_ALWAYS_TRUE(!op_granpos_diff(&delta,
links[li].pcm_end,links[li].pcm_start));
delta-=links[li].head.pre_skip;
pcm_offset+=delta;
}
OP_ASSERT(_li>=0);
if(_of->seekable&&OP_UNLIKELY(op_granpos_cmp(_gp,links[_li].pcm_end)>0)){ if(_of->seekable&&OP_UNLIKELY(op_granpos_cmp(_gp,links[_li].pcm_end)>0)){
_gp=links[_li].pcm_end; _gp=links[_li].pcm_end;
} }
if(OP_LIKELY(op_granpos_cmp(_gp,links[_li].pcm_start)>0)){ if(OP_LIKELY(op_granpos_cmp(_gp,links[_li].pcm_start)>0)){
ogg_int64_t delta;
if(OP_UNLIKELY(op_granpos_diff(&delta,_gp,links[_li].pcm_start)<0)){ if(OP_UNLIKELY(op_granpos_diff(&delta,_gp,links[_li].pcm_start)<0)){
/*This means an unseekable stream claimed to have a page from more than /*This means an unseekable stream claimed to have a page from more than
2 billion days after we joined.*/ 2 billion days after we joined.*/

View file

@ -235,8 +235,7 @@ void *op_fopen(OpusFileCallbacks *_cb,const char *_path,const char *_mode){
fp=fopen(_path,_mode); fp=fopen(_path,_mode);
#else #else
fp=NULL; fp=NULL;
if(_path==NULL||_mode==NULL)errno=EINVAL; {
else{
wchar_t *wpath; wchar_t *wpath;
wchar_t *wmode; wchar_t *wmode;
wpath=op_utf8_to_utf16(_path); wpath=op_utf8_to_utf16(_path);
@ -266,8 +265,7 @@ void *op_freopen(OpusFileCallbacks *_cb,const char *_path,const char *_mode,
fp=freopen(_path,_mode,(FILE *)_stream); fp=freopen(_path,_mode,(FILE *)_stream);
#else #else
fp=NULL; fp=NULL;
if(_path==NULL||_mode==NULL)errno=EINVAL; {
else{
wchar_t *wpath; wchar_t *wpath;
wchar_t *wmode; wchar_t *wmode;
wpath=op_utf8_to_utf16(_path); wpath=op_utf8_to_utf16(_path);

View file

@ -100,7 +100,8 @@ static int op_capi_get_by_subject(X509_LOOKUP *_lu,int _type,X509_NAME *_name,
representation for something, it's the answer that 9 of them would representation for something, it's the answer that 9 of them would
give you back. give you back.
I don't think OpenSSL's encoding qualifies.*/ I don't think OpenSSL's encoding qualifies.*/
find_para.cbData=_name->bytes->length; if(OP_UNLIKELY(_name->bytes->length>MAXDWORD))return 0;
find_para.cbData=(DWORD)_name->bytes->length;
find_para.pbData=(unsigned char *)_name->bytes->data; find_para.pbData=(unsigned char *)_name->bytes->data;
cert=CertFindCertificateInStore(h_store,X509_ASN_ENCODING,0, cert=CertFindCertificateInStore(h_store,X509_ASN_ENCODING,0,
CERT_FIND_SUBJECT_NAME,&find_para,NULL); CERT_FIND_SUBJECT_NAME,&find_para,NULL);
@ -122,7 +123,8 @@ static int op_capi_get_by_subject(X509_LOOKUP *_lu,int _type,X509_NAME *_name,
ret=op_capi_retrieve_by_subject(_lu,_type,_name,_ret); ret=op_capi_retrieve_by_subject(_lu,_type,_name,_ret);
if(ret>0)return ret; if(ret>0)return ret;
memset(&cert_info,0,sizeof(cert_info)); memset(&cert_info,0,sizeof(cert_info));
cert_info.Issuer.cbData=_name->bytes->length; if(OP_UNLIKELY(_name->bytes->length>MAXDWORD))return 0;
cert_info.Issuer.cbData=(DWORD)_name->bytes->length;
cert_info.Issuer.pbData=(unsigned char *)_name->bytes->data; cert_info.Issuer.pbData=(unsigned char *)_name->bytes->data;
memset(&find_para,0,sizeof(find_para)); memset(&find_para,0,sizeof(find_para));
find_para.pCertInfo=&cert_info; find_para.pCertInfo=&cert_info;