Files
Eric F d398a6ced2 Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
2026-06-08 00:38:27 -04:00

273 lines
7.4 KiB
Python

"""
Transport exceptions and functional exceptions that, when raised,
trigger well-defined behaviour from the Shapeshifter UFTP
implementation.
Subclasses of TransportException return the appropriate HTTP Status
Code.
Subclasses of FunctionalException return a proper
PayloadResponseMessage with result = REJECTED and the appropriate
rejection_reason.
More information on these exceptions can be found in the Shapeshifter
Specification. The relevant parts are copied as docstrings for these
exceptions.
"""
from abc import ABC
class TransportException(Exception, ABC):
"""
Base TransportException class that is used by FastApi to return
the approprate status code.
"""
http_status_code: int
class MissingContentLengthException(TransportException):
"""
Thrown when the content-length is missing from the message headers.
"""
http_status_code = 411
class InvalidContentTypeException(TransportException):
"""
Raised when the Content-Type header is not set to text/xml or the
character set is not utf-8.
"""
http_status_code = 400
class TooManyRequestsException(TransportException):
"""
Raised when the originating IP address is making too many requests
to the service.
"""
http_status_code = 429
class SchemaException(TransportException):
"""
Raised when the XML Body cannot be parsed or does not comply to
the schema.
"""
http_status_code = 400
class AuthenticationTimeoutException(TransportException):
"""
Raised when the sender's public key could not be looked up in
DNS.
"""
http_status_code = 419
class InvalidSignatureException(TransportException):
"""
Raised when the signed message could not be unsealed because of an
invalid signature.
"""
http_status_code = 401
class FunctionalException(ABC, Exception):
"""
Base class for gunctional exceptions. When raised in a request
context, FastAPI will return the appropriate response message to
the other participant.
"""
rejection_reason: str
class InvalidMessageException(FunctionalException):
"""
Despite being schema-compliant, the syntax, type or semantics of
the message were unacceptable for the receiving implementation.
"""
def __init__(self, message):
super().__init__()
self.rejection_reason = f"Invalid Message: '{message.__class__.__name__}'"
class InvalidSenderException(FunctionalException):
"""
There is a mismatch between the SenderDomain/Role combination in
the message wrapper and the inner XML message.
"""
rejection_reason = "Invalid Sender"
class UnknownRecipientException(FunctionalException):
"""
The RecipientDomain and/or RecipientRole specified in the inner
XML message is not handled by this endpoint.
"""
rejection_reason = "Unknown Recipient"
class BarredSenderException(FunctionalException):
"""
This endpoint is explicitly blocking messages from this sender.
"""
rejection_reason = "Barred Sender"
class DuplicateIdentifierException(FunctionalException):
"""
The MessageID attribute of the inner XML message is not unique,
and has already been used for a message with different content.
This message has been rejected.
"""
rejection_reason = "Duplicate Identifier"
class AlreadySubmittedException(FunctionalException):
"""
The MessageID attribute of the inner XML message is not unique,
but since the message content is the same as that of a previously
accepted message, this copy can be considered to be successfully
submitted as well.
"""
rejection_reason = "Already Submitted"
class ISPDurationRejectedException(FunctionalException):
"""
The message specifies a ISP duration that is not the agreed-upon
common value for the market in which it is used.
"""
rejection_reason = "ISP Duration Rejected"
class TimeZoneRejectedException(FunctionalException):
"""
The message specifies a time zone that has a different UTC offset
than is the agreed-upon common value for the market.
"""
rejection_reason = "TimeZone Rejected"
class InvalidCongestionPointException(FunctionalException):
"""
Unknown congestion point or the recipient is not active at this
congestion point.
"""
rejection_reason = "Invalid Congestion Point"
class UnknownReferenceException(FunctionalException):
"""
The message with the sequence where is referred to is unknown. For
the concerning reference field name can be filled in (for example
FlexRequestSequence or PrognosisSequence).
"""
rejection_reason = "Unknown Reference"
class ReferencePeriodMismatchException(FunctionalException):
"""
The message(s) with the sequence where is referred to contains a
different Period.
"""
rejection_reason = "Reference Period Mismatch"
class ReferenceMessageExpiredException(FunctionalException):
"""
The message that is referred to is expired.
"""
rejection_reason = "Reference Message Expired"
class ReferenceMessageRevokedException(FunctionalException):
"""
The message that is referred to is revoked.
"""
rejection_reason = "Reference Message Revoked"
class ISPsOutOfBoundsException(FunctionalException):
"""
One or more ISPs are outside the tolerated boundaries: ISPs do not
exist.
"""
rejection_reason = "ISPs Out Of Bounds"
class ISPConflictException(FunctionalException):
"""
One or more ISPs are defined more than once, possibly because of
an incorrect duration.
"""
rejection_reason = "ISP Conflict"
class PeriodOutOfBoundsException(FunctionalException):
"""
Period of the message is inappropriate. For example: a FlexRequest
with a Period in the past or a settlement item in a
FlexSettlement with a Period outside the concerning settlement
period.
"""
rejection_reason = "Period Out Of Bounds"
class ExpirationDateTimeOutOfBoundsException(FunctionalException):
"""
ExpirationDateTime is in the past or exceeds the ISPs in the
message.
"""
rejection_reason = "Expiration DateTime Out Of Bounds"
class UnauthorizedException(FunctionalException):
"""
CRO is operating in closed mode and the DSO is not pre-registered
as an authorized participant
"""
rejection_reason = "Unauthorized"
class ConnectionConflictException(FunctionalException):
"""
A connection is transmitted before at another Congestion Point.
Return EntityAddress of the concerning Connection and Congestion
Point where it has been placed before.
"""
def __init__(self, connection_entity_address, congestion_point_entity_address):
super().__init__()
self.rejection_reason = (
f"Connection conflict: {connection_entity_address} at {congestion_point_entity_address}"
)
class SubordinateSequenceNumberException(FunctionalException):
"""
The message sequence is lower than that of a previously received
DSOPortfolioUpdate
"""
rejection_reason = "Subordinate Sequence Number"
class ServiceDiscoveryException(Exception):
"""
Raised when there is an error during service discovery.
"""
class ClientTransportException(Exception):
"""
Raised when the response to the client is not HTTP 200.
"""
def __init__(self, *args, response, **kwargs):
self.response = response
super().__init__(*args, **kwargs)